From 747a92f534fb0867411c0b61335e73784cc51141 Mon Sep 17 00:00:00 2001 From: Sergey Sergeev Date: Mon, 13 May 2024 11:40:05 -0700 Subject: [PATCH] add support for snapshot storage in orion Knowledge and fs (#32) * add support for snapshot storage in orion Knowledge and fs --- package-lock.json | 34 +++- package.json | 3 +- src/workflow/Snapshot.js | 26 --- src/workflow/SnapshotManager.js | 325 ++++++++++++++++++++++++++++++++ src/workflow/StatedWorkflow.js | 4 +- src/workflow/WorkflowManager.js | 43 ++++- stated-workflow-api.js | 22 ++- yarn.lock | 26 ++- 8 files changed, 436 insertions(+), 47 deletions(-) delete mode 100644 src/workflow/Snapshot.js create mode 100644 src/workflow/SnapshotManager.js diff --git a/package-lock.json b/package-lock.json index ee4dee7..c51deea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,13 +18,14 @@ "kafkajs": "^2.2.4", "kafkajs-lz4": "^1.2.1", "pulsar-client": "^1.9.0", - "stated-js": "^0.1.20" + "stated-js": "^0.1.23" }, "bin": { "stateflow": "stated-workflow.js" }, "devDependencies": { "jest": "^29.7.0", + "jest-fetch-mock": "^3.0.3", "wtfnode": "^0.9.1" } }, @@ -2115,6 +2116,15 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/cross-fetch": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "dev": true, + "dependencies": { + "node-fetch": "^2.6.12" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -3271,6 +3281,16 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-fetch-mock": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz", + "integrity": "sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==", + "dev": true, + "dependencies": { + "cross-fetch": "^3.0.4", + "promise-polyfill": "^8.1.3" + } + }, "node_modules/jest-get-type": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", @@ -4465,6 +4485,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/promise-polyfill": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz", + "integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==", + "dev": true + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -4919,9 +4945,9 @@ } }, "node_modules/stated-js": { - "version": "0.1.20", - "resolved": "https://registry.npmjs.org/stated-js/-/stated-js-0.1.20.tgz", - "integrity": "sha512-jN2c3mKxqiEKr0h/TSqwfWQs0nL8AOZUDzCZt7Awo88j68EeAUBXDYr3xPA7UHIatV0UV5+J0TmxUsEbFccXJw==", + "version": "0.1.23", + "resolved": "https://registry.npmjs.org/stated-js/-/stated-js-0.1.23.tgz", + "integrity": "sha512-r8ntZm4hHY0AVhlapAVAi+eqGJZM8oDTtrR29n9FEslbdFO4gFCyeIPWNe6H3/TcH6KNmqUBFiXu4wo/AEZlng==", "dependencies": { "chalk": "^5.3.0", "flatbuffers": "^23.5.26", diff --git a/package.json b/package.json index 4676b1a..71b8c1d 100644 --- a/package.json +++ b/package.json @@ -37,10 +37,11 @@ "kafkajs": "^2.2.4", "kafkajs-lz4": "^1.2.1", "pulsar-client": "^1.9.0", - "stated-js": "^0.1.20" + "stated-js": "^0.1.23" }, "devDependencies": { "jest": "^29.7.0", + "jest-fetch-mock": "^3.0.3", "wtfnode": "^0.9.1" }, "files": [ diff --git a/src/workflow/Snapshot.js b/src/workflow/Snapshot.js deleted file mode 100644 index b66835b..0000000 --- a/src/workflow/Snapshot.js +++ /dev/null @@ -1,26 +0,0 @@ -import { promises as fs } from 'fs'; - -export class Snapshot { - - static async write(tp) { - const {snapshot: snapshotOpts} = tp.options; - if(!snapshotOpts){ - tp.logger.debug("no --snapshot options defined, skipping snapshot"); - return; - } - const snapshotStr = await tp.snapshot(); - const {storage = "fs", path = "./defaultSnapshot.json"} = snapshotOpts; // Default path if not provided - - if (storage === "fs") { - try { - await fs.writeFile(path, snapshotStr); - tp.logger.info(`Snapshot saved to ${path}`); - } catch (error) { - console.error(`Failed to save snapshot to ${path}:`, error); - throw error; - } - } else { - tp.logger.info('Storage method not supported.'); - } - } -} diff --git a/src/workflow/SnapshotManager.js b/src/workflow/SnapshotManager.js new file mode 100644 index 0000000..27edbe8 --- /dev/null +++ b/src/workflow/SnapshotManager.js @@ -0,0 +1,325 @@ +import { promises as fs } from 'fs'; +import StatedREPL from "stated-js/dist/src/StatedREPL.js"; + +export class SnapshotManager { + + constructor(options = {storage: 'fs', state: "./.state"}) { + this.options = options; + this.snapshots = []; + } + + async load() { + if (this.options.storage === 'knowledge') { + this.snapshots = await SnapshotManager.loadFromKnowledge(); + } else if (this.options.storage === 'fs') { + await this.loadFromFS(); + } else { + throw new Error('Storage method not supported.'); + } + return this.snapshots; + } + + static async write(tp) { + const {snapshot: snapshotOpts} = tp.options; + if(!snapshotOpts){ + tp.logger.debug("no --snapshot options defined, skipping snapshot"); + return; + } + const snapshotStr = await tp.snapshot(); + const {storage = "fs", path = "./defaultSnapshot.json"} = snapshotOpts; // Default path if not provided + + if (storage === "fs") { + try { + await fs.writeFile(path, snapshotStr); + tp.logger.info(`Snapshot saved to ${path}`); + } catch (error) { + console.error(`Failed to save snapshot to ${path}:`, error); + throw error; + } + } if (storage === "knowledge") { + await SnapshotManager.writeToKnowledge(snapshotStr); + } else { + tp.logger.info('Storage method not supported.'); + } + } + + /** + * curl -X 'POST' -d '{ + * "id":"homeWorldSnapshotExample", + * "snapshot": { + * "options":{"importPath":"./stated-workflow","snapshot":{"seconds":1}}, + * "output":{"action":"{function:}","subscribe$":"'\''listening clientType=test'\''","subscribeParams":{"client":{"type":"test"},"source":"cloudEvent","subscriberId":"alertingActionWorkflow","to":"{function:}","type":"alertingTrigger"},"triggers":[]}, + * "template":{"action":"${ function($trigger){( $console.log($trigger); $set('\''/triggers/-'\'', $trigger); $trigger)}}","subscribe$":"$subscribe(subscribeParams)","subscribeParams":{"client":{"type":"test"},"source":"cloudEvent","subscriberId":"alertingActionWorkflow","to":"/${action}","type":"alertingTrigger"},"triggers":[]}}, + * "type":"snapshot" + * }' + * -H 'Accept: application/json' -H 'Content-Type: application/json' -H 'Layer-Id: 2d4866c4-0a45-41ec-a534-011e5f4d970a' -H 'Layer-Type: TENANT' + * 'https://localhost:8081/knowledge-store/v2beta/objects/sesergeeworkflow:snapshot' + * + * @param snapshotStr + * @param snapshotOpts + * @returns {Promise} + */ + static async writeToKnowledge(snapshotStr) { + if (!snapshotStr) { + throw new Error('Snapshot string is required'); + } + const snapshot = JSON.parse(snapshotStr); + // workaround for the zodiac gateway + + const APPD_JSON_STORE_URL = process.env.APPD_JSON_STORE_URL || 'http://localhost:8081/knowledge-store'; + const objectPath = '/v2beta/objects/sesergeeworkflow:snapshot'; + const url = `${APPD_JSON_STORE_URL}${objectPath}`; + + // Sample headers used in both environments + const headers = { + 'Accept': 'application/json', + 'Layer-Id': '2d4866c4-0a45-41ec-a534-011e5f4d970a', + 'Layer-Type': 'TENANT', + 'appd-pty': 'IlVTRVIi', + 'appd-pid': 'ImZvb0BiYXIuY29tIg==' + }; + + const data = StatedREPL.stringify({ + snapshot: snapshot, + id: snapshot.options.snapshot.id, + type: 'snapshot' + }) + console.log(`attempting to save snapshot: ${data}`); + + // send the snapshot data to the knowledge store url (method POST) + const result = await fetch(url, { + method: 'POST', + headers: { + ...headers, + 'Content-Type': 'application/json' + }, + body: data + }); + + if (!result.ok) { + console.log(`Failed to save snapshot: ${result.status}:${result.statusText}`); + } + } + + /** + * This method attempts to load snapshots from the COP knowledge Store APIs. + * + * It can run as a Zodiac function or on a developer laptop with support of fsoc proxy. + * + * A typical request from the laptop: + * curl -X 'GET' -H 'Accept: application/json' -H 'Layer-Id: 2d4866c4-0a45-41ec-a534-011e5f4d970a' -H 'Layer-Type: TENANT' -H'appd-pty: IlVTRVIi' -H'appd-pid: ImZvb0BiYXIuY29tIg==' localhost:8081/knowledge-store/v2beta/objects/sesergeeworkflow:snapshot/IOrRxuuAqh + * + * From a zodiac function: + * curl -X 'GET' -H 'Accept: application/json' -H 'Layer-Id: 2d4866c4-0a45-41ec-a534-011e5f4d970a' -H 'Layer-Type: TENANT' -H'appd-pty: IlVTRVIi' -H'appd-pid: ImZvb0BiYXIuY29tIg==' $APPD_JSON_STORE_URL/v2beta/objects/sesergeeworkflow:snapshot/IOrRxuuAqh + * + * it returns am array of snapshot objects (an example of a response is below is below) + * { + * "total": 2, + * "items": [ + * { + * "layerType": "TENANT", + * "id": "IOrRxuuAqh", + * "layerId": "2d4866c4-0a45-41ec-a534-011e5f4d970a", + * "data": { + * "id": "homeWorldSnapshotExample", + * "type": "snapshot", + * "snapshot": { + * "output": { + * "action": "{function:}", + * "triggers": [], + * "subscribeParams": { + * "to": "{function:}", + * "type": "alertingTrigger", + * "client": { + * "type": "test" + * }, + * "source": "cloudEvent", + * "subscriberId": "alertingActionWorkflow" + * } + * }, + * "options": { + * "snapshot": { + * "seconds": 1 + * }, + * "importPath": "./stated-workflow" + * }, + * "template": { + * "action": "${ function($trigger){( $console.log($trigger); $set('/triggers/-', $trigger); $trigger)}}", + * "triggers": [], + * "subscribe$": "$subscribe(subscribeParams)", + * "subscribeParams": { + * "to": "/${action}", + * "type": "alertingTrigger", + * "client": { + * "type": "test" + * }, + * "source": "cloudEvent", + * "subscriberId": "alertingActionWorkflow" + * } + * } + * } + * }, + * "objectMimeType": "application/json", + * "targetObjectId": null, + * "patch": null, + * "objectVersion": 1, + * "blobInfo": null, + * "createdAt": "2024-05-06T21:26:45.836Z", + * "updatedAt": "2024-05-06T21:26:45.836Z", + * "objectType": "sesergeeworkflow:snapshot", + * "fqid": "sesergeeworkflow:snapshot/IOrRxuuAqh;layerId=2d4866c4-0a45-41ec-a534-011e5f4d970a;layerType=TENANT" + * }, + * { + * "layerType": "TENANT", + * "id": "zq5bo7eVmi", + * "layerId": "2d4866c4-0a45-41ec-a534-011e5f4d970a", + * "data": { + * "id": "homeWorldSnapshotExample", + * "type": "snapshot", + * "snapshot": { + * "output": { + * "action": "{function:}", + * "triggers": [], + * "subscribe$": "'listening clientType=test'", + * "subscribeParams": { + * "to": "{function:}", + * "type": "alertingTrigger", + * "client": { + * "type": "test" + * }, + * "source": "cloudEvent", + * "subscriberId": "alertingActionWorkflow" + * } + * }, + * "options": { + * "snapshot": { + * "seconds": 1 + * }, + * "importPath": "./stated-workflow" + * }, + * "template": { + * "action": "${ function($trigger){( $console.log($trigger); $set('/triggers/-', $trigger); $trigger)}}", + * "triggers": [], + * "subscribe$": "$subscribe(subscribeParams)", + * "subscribeParams": { + * "to": "/${action}", + * "type": "alertingTrigger", + * "client": { + * "type": "test" + * }, + * "source": "cloudEvent", + * "subscriberId": "alertingActionWorkflow" + * } + * } + * } + * }, + * "objectMimeType": "application/json", + * "targetObjectId": null, + * "patch": null, + * "objectVersion": 1, + * "blobInfo": null, + * "createdAt": "2024-05-06T22:50:54.143Z", + * "updatedAt": "2024-05-06T22:50:54.143Z", + * "objectType": "sesergeeworkflow:snapshot", + * "fqid": "sesergeeworkflow:snapshot/zq5bo7eVmi;layerId=2d4866c4-0a45-41ec-a534-011e5f4d970a;layerType=TENANT" + * } + * ] + * } + * + * the fallback to zodiac function API call happens if $APPD_JSON_STORE_URL env variable is set + * @returns {Promise} + */ + static async loadFromKnowledge() { + // Get the environment variables for the Zodiac platform API endpoint + const APPD_JSON_STORE_URL = process.env.APPD_JSON_STORE_URL || 'http://localhost:8081/knowledge-store'; + + // Sample headers used in both environments + const headers = { + 'Accept': 'application/json', + 'Layer-Id': '2d4866c4-0a45-41ec-a534-011e5f4d970a', + 'Layer-Type': 'TENANT', + 'appd-pty': 'IlVTRVIi', + 'appd-pid': 'ImZvb0BiYXIuY29tIg==' + }; + + // Define the specific object path, modify as needed + const objectPath = '/v2beta/objects/sesergeeworkflow:snapshot'; + + // Create the full URL + const url = `${APPD_JSON_STORE_URL}${objectPath}`; + + try { + // Fetch the snapshot data + const response = await fetch(url, { method: 'GET', headers }); + + if (!response.ok) { + throw new Error(`Failed to load snapshots: ${response.statusText}`); + } + + // Parse the JSON response + const data = await response.json(); + + // Ensure it is an array as described + if (!Array.isArray(data.items)) { + throw new Error('Unexpected data format: Expected an array of snapshot objects'); + } + + return data.items.map(item => item.data); + } catch (error) { + console.error('Error loading snapshots:', error.message); + throw error; + } + } + + /** + * Load snapshots from the file system state + * + * this method walks through this.options.state directory and loads and stores all the snapshots + **/ + async loadFromFS() { + const files = await fs.readdir(this.options.state); + for (const file of files) { + const snapshotContent = await fs.readFile(`${this.options.state}/${file}`, 'utf8'); + try { + this.snapshots.push(JSON.parse(snapshotContent)); + } catch (error) { + console.error(`Failed to parse snapshot ${file}:`, error); + } + } + } + + static async readFromKnowledge(workflowId) { + + // Get the environment variables for the Zodiac platform API endpoint + const APPD_JSON_STORE_URL = process.env.APPD_JSON_STORE_URL || 'http://localhost:8081/knowledge-store'; + + // Sample headers used in both environments + const headers = { + 'Accept': 'application/json', + 'Layer-Id': '2d4866c4-0a45-41ec-a534-011e5f4d970a', + 'Layer-Type': 'TENANT', + 'appd-pty': 'IlVTRVIi', + 'appd-pid': 'ImZvb0BiYXIuY29tIg==' + }; + + // Define the specific object path, modify as needed + const objectPath = '/v2beta/objects/sesergeeworkflow:snapshot/' + workflowId; + + // Create the full URL + const url = `${APPD_JSON_STORE_URL}${objectPath}`; + + try { + // Fetch the snapshot data + const response = await fetch(url, { method: 'GET', headers }); + + if (!response.ok) { + throw new Error(`Failed to load snapshots: ${response.statusText}`); + } + + return await response.json(); + } catch (error) { + console.error('Error loading snapshots:', error.message); + throw error; + } + } +} diff --git a/src/workflow/StatedWorkflow.js b/src/workflow/StatedWorkflow.js index a59f468..3e65ba2 100644 --- a/src/workflow/StatedWorkflow.js +++ b/src/workflow/StatedWorkflow.js @@ -23,7 +23,7 @@ import Step from "./Step.js"; import {TemplateUtils} from "./utils/TemplateUtils.js"; import {WorkflowPersistence} from "./WorkflowPersistence.js"; import {Delay} from "../test/TestTools.js" -import {Snapshot} from "./Snapshot.js"; +import {SnapshotManager} from "./SnapshotManager.js"; import {PulsarClientMock} from "../test/PulsarMock.js"; import {SchemaRegistry} from "@kafkajs/confluent-schema-registry"; import LZ4 from "kafkajs-lz4"; @@ -108,7 +108,7 @@ export class StatedWorkflow { const {seconds = 1} = snapshotOpts; this.snapshotInterval = setInterval(async ()=>{ if(this.hasChanged){ - await Snapshot.write(this.templateProcessor); + await SnapshotManager.write(this.templateProcessor); // we can acknowledge callbacks after persisting templateProcessor if (workflowContext.ackOnSnapshot === true && this.workflowDispatcher) await this.workflowDispatcher.acknowledgeCallbacks(); this.hasChanged = false; //changeListener will alter this if the template changes so we are not permanently blocking snapshots diff --git a/src/workflow/WorkflowManager.js b/src/workflow/WorkflowManager.js index a81a758..8dbadf4 100644 --- a/src/workflow/WorkflowManager.js +++ b/src/workflow/WorkflowManager.js @@ -6,15 +6,33 @@ import {WorkflowMetrics} from "./WorkflowMeters.js"; import fs from "fs"; import util from "util"; +import {SnapshotManager} from "./SnapshotManager.js"; const mkdir = util.promisify(fs.mkdir); -// WorkflowManager.js.js export class WorkflowManager { - constructor() { + + constructor(options = {'snapshot': { + storage: 'fs', + }}) { this.workflows = {}; this.dispatchersByType = {}; + this.options = options; this.workflowMetrics = new WorkflowMetrics(); this.statePath = './.state'; + this.snapshot = new SnapshotManager(this.options.snapshot) + } + + async initialize() { + await this.snapshot.load(); + for (const snapshot of this.snapshot.snapshots) { + try { + console.log(`Restoring workflow ${StatedREPL.stringify(snapshot)}`); + // await this.restoreWorkflow(snapshot.snapshot); + } catch (error) { + console.error(`Error restoring workflow ${StatedREPL.stringify(snapshot)}: ${error}`); + } + } + } async createTypesMap(sw) { @@ -35,7 +53,11 @@ export class WorkflowManager { const workflowId = WorkflowManager.generateUniqueId(); const sw = await StatedWorkflow.newWorkflow(template, context, {cbmon: this.workflowMetrics.monitorCallback(workflowId), ackOnSnapshot: true}); - sw.templateProcessor.options = {'snapshot': {'snapshotIntervalSeconds': 1, path: `${this.statePath}/${workflowId}.json`}}; + this.options.workflowId = workflowId; + if (!this.options.snapshotIntervalSeconds) { + this.options.snapshotIntervalSeconds = 1; + } + sw.templateProcessor.options = this.options; await this.ensureStatePathDir(); this.workflows[workflowId] = sw; await sw.templateProcessor.initialize(template) @@ -134,14 +156,18 @@ export class WorkflowManager { } - async getWorkflowSnapshot(workflowId) { + async getWorkflowSnapshot(workflowId, storage) { console.log(`Reading snapshot object with ID ${workflowId}`); + if (storage === 'knowledge') { + const snapshot = await SnapshotManager.readFromKnowledge(workflowId); + return snapshot; + } const snapshotContent = fs.readFileSync(`${this.statePath}/${workflowId}.json`, 'utf8'); return JSON.parse(snapshotContent); } - async restoreWorkflow(workflowId) { + async restoreWorkflowFromFile(workflowId) { const snapshotContent = fs.readFileSync(`${this.statePath}/${workflowId}.json`, 'utf8'); const snapshot = JSON.parse(snapshotContent); @@ -151,9 +177,14 @@ export class WorkflowManager { console.log(`Closing ${workflowId} workflow`); await this.workflows[workflowId].close(); } + + return this.restoreWorkflow(snapshot); + } + + async restoreWorkflow(snapshot, workflowId) { await TemplateProcessor.prepareSnapshotInPlace(snapshot); const sw = await StatedWorkflow.newWorkflow(snapshot.template); - this.workflows[workflowId] = sw; + this.workflows[workflowId || WorkflowManager.generateUniqueId()] = sw; sw.templateProcessor.options = snapshot.options; await sw.templateProcessor.initialize(snapshot.template, '/', snapshot.output); } diff --git a/stated-workflow-api.js b/stated-workflow-api.js index 2af4457..ac8148b 100755 --- a/stated-workflow-api.js +++ b/stated-workflow-api.js @@ -3,11 +3,21 @@ import express from 'express'; import bodyParser from 'body-parser'; import { WorkflowManager } from "./src/workflow/WorkflowManager.js"; import StatedREPL from "stated-js/dist/src/StatedREPL.js"; +import minimist from 'minimist'; const app = express(); app.use(bodyParser.json()); -const workflowManager = new WorkflowManager(); + +const args = minimist(process.argv.slice(2)); + +let snapshotOptions = undefined; +if (args.storage) { + snapshotOptions = {snapshot: {storage: args.storage}}; +}; +const workflowManager = new WorkflowManager(snapshotOptions); +await workflowManager.initialize(); + app.get('/', (req, res) => { res.json({ status: 'OK' }); @@ -33,7 +43,7 @@ app.post('/', async (req, res) => { } }); -app.post('/workflow', async (req, res) => { +app.post('/workflow', async (req/**/, res) => { try { const workflowId = await workflowManager.createWorkflow(req.body); res.json({ workflowId, status: 'Started' }); @@ -48,7 +58,7 @@ app.get('/workflow', (req, res ) => { }); app.get('/workflow/:workflowId', (req, res) => { - const workflowId = req.params.workflowId; + const workflowId = req.params.workflowId;/**/ const workflow = workflowManager.getWorkflow(workflowId); if (workflow) { res.json(workflow.templateProcessor.output); @@ -102,7 +112,7 @@ app.get('/restore/:workflowId', (req, res) => { const workflowId = req.params.workflowId; console.log(`Received GET /restore/${workflowId}`); try { - res.json(workflowManager.getWorkflowSnapshot(workflowId)); + res.json(workflowManager.getWorkflowSnapshot(workflowId, storage)); } catch (error) { console.error(`Error in GET /restore/${workflowId}`, error); res.status(500).send({'error': error.toString()}); @@ -114,7 +124,7 @@ app.post('/restore/:workflowId', async (req, res) => { const workflowId = req.params.workflowId; console.log(`Received POST /restore/${workflowId} with data:`, req.body); try { - await workflowManager.restoreWorkflow(workflowId, req.body); + await workflowManager.restoreWorkflowFromFile(workflowId, req.body); res.json({ workflowId, status: 'restored' }); } catch (error) { console.error(`Error in POST /workflow/${workflowId}`, error); @@ -124,6 +134,8 @@ app.post('/restore/:workflowId', async (req, res) => { app.listen(8080, () => { console.log('Server running on port 8080'); +}).on('error', (error) => { + console.error('Error starting server:', error); }); app.post('/event', async (req, res) => { diff --git a/yarn.lock b/yarn.lock index d39695e..7a26044 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1230,6 +1230,13 @@ create-jest@^29.7.0: jest-util "^29.7.0" prompts "^2.0.1" +cross-fetch@^3.0.4: + version "3.1.8" + resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz" + integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg== + dependencies: + node-fetch "^2.6.12" + cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" @@ -1939,6 +1946,14 @@ jest-environment-node@^29.7.0: jest-mock "^29.7.0" jest-util "^29.7.0" +jest-fetch-mock@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz" + integrity sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw== + dependencies: + cross-fetch "^3.0.4" + promise-polyfill "^8.1.3" + jest-get-type@^29.6.3: version "29.6.3" resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz" @@ -2478,7 +2493,7 @@ node-addon-api@^4.3.0: resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz" integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== -node-fetch@^2.6.7: +node-fetch@^2.6.12, node-fetch@^2.6.7: version "2.7.0" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -2659,6 +2674,11 @@ pretty-format@^29.7.0: ansi-styles "^5.0.0" react-is "^18.0.0" +promise-polyfill@^8.1.3: + version "8.3.0" + resolved "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz" + integrity sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg== + prompts@^2.0.1: version "2.4.2" resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz" @@ -2951,9 +2971,9 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" -stated-js@^0.1.20: +stated-js@^0.1.23: version "0.1.23" - resolved "https://registry.yarnpkg.com/stated-js/-/stated-js-0.1.23.tgz#b2526a7bb1cb6cbf59a6bbb0da8dbdbfe71542ab" + resolved "https://registry.npmjs.org/stated-js/-/stated-js-0.1.23.tgz" integrity sha512-r8ntZm4hHY0AVhlapAVAi+eqGJZM8oDTtrR29n9FEslbdFO4gFCyeIPWNe6H3/TcH6KNmqUBFiXu4wo/AEZlng== dependencies: chalk "^5.3.0"