diff --git a/package-lock.json b/package-lock.json index f1888558..a93069da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,9 +20,9 @@ "@map-colonies/schemas": "^1.13.0", "@map-colonies/telemetry": "^10.1.0", "@opentelemetry/api": "^1.9.0", - "@prisma/adapter-pg": "^6.19.0", - "@prisma/client": "^6.19.0", - "@prisma/instrumentation": "^6.19.0", + "@prisma/adapter-pg": "^7.0.1", + "@prisma/client": "^7.0.1", + "@prisma/instrumentation": "^7.0.1", "compression": "^1.8.1", "date-fns": "^4.1.0", "express": "^4.21.2", @@ -70,8 +70,8 @@ "jest-openapi": "^0.14.2", "prettier": "^3.3.3", "pretty-quick": "^4.1.1", - "prisma": "^6.19.0", - "prisma-json-types-generator": "^3.6.2", + "prisma": "^7.0.1", + "prisma-json-types-generator": "^4.0.0-beta.1", "rimraf": "^6.1.0", "supertest": "^7.1.4", "tsc-alias": "^1.8.11", @@ -201,6 +201,43 @@ "node": ">=6.9.0" } }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.5.0.tgz", + "integrity": "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "node_modules/@chevrotain/gast": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-10.5.0.tgz", + "integrity": "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "node_modules/@chevrotain/types": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-10.5.0.tgz", + "integrity": "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/utils": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-10.5.0.tgz", + "integrity": "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==", + "devOptional": true, + "license": "Apache-2.0" + }, "node_modules/@commitlint/cli": { "version": "20.1.0", "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-20.1.0.tgz", @@ -612,6 +649,36 @@ "node": ">=v18" } }, + "node_modules/@electric-sql/pglite": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.2.tgz", + "integrity": "sha512-zfWWa+V2ViDCY/cmUfRqeWY1yLto+EpxjXnZzenB1TyxsTiXaTWeZFIZw6mac52BsuQm0RjCnisjBtdBaXOI6w==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@electric-sql/pglite-socket": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.0.6.tgz", + "integrity": "sha512-6RjmgzphIHIBA4NrMGJsjNWK4pu+bCWJlEWlwcxFTVY3WT86dFpKwbZaGWZV6C5Rd7sCk1Z0CI76QEfukLAUXw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "pglite-server": "dist/scripts/server.js" + }, + "peerDependencies": { + "@electric-sql/pglite": "0.3.2" + } + }, + "node_modules/@electric-sql/pglite-tools": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.2.7.tgz", + "integrity": "sha512-9dAccClqxx4cZB+Ar9B+FZ5WgxDc/Xvl9DPrTWv+dYTf0YNubLzi4wHHRGRGhrJv15XwnyKcGOZAP1VXSneSUg==", + "devOptional": true, + "license": "Apache-2.0", + "peerDependencies": { + "@electric-sql/pglite": "0.3.2" + } + }, "node_modules/@emnapi/core": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.4.tgz", @@ -1435,6 +1502,19 @@ "node": ">=6" } }, + "node_modules/@hono/node-server": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.14.2.tgz", + "integrity": "sha512-GHjpOeHYbr9d1vkID2sNUYkl5IxumyhDrUJB7wBp7jvqYwPFt+oNKsAPBRcdSbV7kIrXhouLE199ks1QcK4r7A==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1928,7 +2008,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", "license": "MIT", - "peer": true, "engines": { "node": ">=20" } @@ -1956,8 +2035,7 @@ "version": "1.13.0", "resolved": "https://registry.npmjs.org/@map-colonies/schemas/-/schemas-1.13.0.tgz", "integrity": "sha512-1b2vILIGn9BxV89rzZWGccGytIVeBeYthB9NdxHjPaxBUPviFBJDDJqVOxBrQ4YhXxjEl5jWj/FPadix0shOJw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@map-colonies/telemetry": { "version": "10.1.0", @@ -2019,6 +2097,20 @@ "typescript": ">= 5.5.2" } }, + "node_modules/@mrleebo/prisma-ast": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@mrleebo/prisma-ast/-/prisma-ast-0.12.1.tgz", + "integrity": "sha512-JwqeCQ1U3fvccttHZq7Tk0m/TMC6WcFAQZdukypW3AzlJYKYTGNVd1ANU2GuhKnv4UQuOFj3oAl0LLG/gxFN1w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "chevrotain": "^10.5.0", + "lilconfig": "^2.1.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", @@ -2097,7 +2189,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=8.0.0" } @@ -3535,29 +3626,30 @@ "license": "MIT" }, "node_modules/@prisma/adapter-pg": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-6.19.0.tgz", - "integrity": "sha512-F8f2kvU+igmxERA9oM+D2maMaxrST1P6vO7ayvdlAdcJI47nKNPdkLBGZKic9otghfA9CQnjNOGB56q2VXqnHw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.0.1.tgz", + "integrity": "sha512-01GpPPhLMoDMF4ipgfZz0L87fla/TV/PBQcmHy+9vV1ml6gUoqF8dUIRNI5Yf2YKpOwzQg9sn8C7dYD1Yio9Ug==", "license": "Apache-2.0", "dependencies": { - "@prisma/driver-adapter-utils": "6.19.0", - "pg": "^8.11.3", + "@prisma/driver-adapter-utils": "7.0.1", + "pg": "^8.16.3", "postgres-array": "3.0.4" } }, "node_modules/@prisma/client": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.19.0.tgz", - "integrity": "sha512-QXFT+N/bva/QI2qoXmjBzL7D6aliPffIwP+81AdTGq0FXDoLxLkWivGMawG8iM5B9BKfxLIXxfWWAF6wbuJU6g==", - "hasInstallScript": true, + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.0.1.tgz", + "integrity": "sha512-O74T6xcfaGAq5gXwCAvfTLvI6fmC3and2g5yLRMkNjri1K8mSpEgclDNuUWs9xj5AwNEMQ88NeD3asI+sovm1g==", "license": "Apache-2.0", - "peer": true, + "dependencies": { + "@prisma/client-runtime-utils": "7.0.1" + }, "engines": { - "node": ">=18.18" + "node": "^20.19 || ^22.12 || >=24.0" }, "peerDependencies": { "prisma": "*", - "typescript": ">=5.1.0" + "typescript": ">=5.4.0" }, "peerDependenciesMeta": { "prisma": { @@ -3568,10 +3660,16 @@ } } }, + "node_modules/@prisma/client-runtime-utils": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.0.1.tgz", + "integrity": "sha512-R26BVX9D/iw4toUmZKZf3jniM/9pMGHHdZN5LVP2L7HNiCQKNQQx/9LuMtjepbgRqSqQO3oHN0yzojHLnKTGEw==", + "license": "Apache-2.0" + }, "node_modules/@prisma/config": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.19.0.tgz", - "integrity": "sha512-zwCayme+NzI/WfrvFEtkFhhOaZb/hI+X8TTjzjJ252VbPxAl2hWHK5NMczmnG9sXck2lsXrxIZuK524E25UNmg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.0.1.tgz", + "integrity": "sha512-MacIjXdo+hNKxPvtMzDXykIIc8HCRWoyjQ2nguJTFqLDzJBD5L6QRaANGTLOqbGtJ3sFvLRmfXhrFg3pWoK1BA==", "devOptional": true, "license": "Apache-2.0", "dependencies": { @@ -3582,129 +3680,241 @@ } }, "node_modules/@prisma/debug": { - "version": "6.17.1", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.17.1.tgz", - "integrity": "sha512-Vf7Tt5Wh9XcndpbmeotuqOMLWPTjEKCsgojxXP2oxE1/xYe7PtnP76hsouG9vis6fctX+TxgmwxTuYi/+xc7dQ==", - "dev": true, + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.0.1.tgz", + "integrity": "sha512-5+25XokVeAK2Z2C9W457AFw7Hk032Q3QI3G58KYKXPlpgxy+9FvV1+S1jqfJ2d4Nmq9LP/uACrM6OVhpJMSr8w==", "license": "Apache-2.0" }, + "node_modules/@prisma/dev": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.13.0.tgz", + "integrity": "sha512-QMmF6zFeUF78yv1HYbHvod83AQnl7u6NtKyDhTRZOJup3h1icWs8R7RUVxBJZvM2tBXNAMpLQYYM/8kPlOPegA==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "@electric-sql/pglite": "0.3.2", + "@electric-sql/pglite-socket": "0.0.6", + "@electric-sql/pglite-tools": "0.2.7", + "@hono/node-server": "1.14.2", + "@mrleebo/prisma-ast": "0.12.1", + "@prisma/get-platform": "6.8.2", + "@prisma/query-plan-executor": "6.18.0", + "foreground-child": "3.3.1", + "get-port-please": "3.1.2", + "hono": "4.7.10", + "http-status-codes": "2.3.0", + "pathe": "2.0.3", + "proper-lockfile": "4.1.2", + "remeda": "2.21.3", + "std-env": "3.9.0", + "valibot": "1.1.0", + "zeptomatch": "2.0.2" + } + }, + "node_modules/@prisma/dev/node_modules/get-port-please": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.1.2.tgz", + "integrity": "sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@prisma/dev/node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "devOptional": true, + "license": "MIT" + }, "node_modules/@prisma/dmmf": { - "version": "6.17.1", - "resolved": "https://registry.npmjs.org/@prisma/dmmf/-/dmmf-6.17.1.tgz", - "integrity": "sha512-ILf+sYTvoZRJIn1KjW8ZBn9N3fK+4ZxreJT0EXuLxbZWbXuz7Eums0/PEscS8+fzktg1zbxZvjHVFMppqfFz3g==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/dmmf/-/dmmf-7.0.1.tgz", + "integrity": "sha512-8f04R3226L/tvC0jMuTRF9ArbYU/AWdAClkw7XCcSrN1Jml/zWt+43OOwAi5K/7EpASRi+IaaIdrmT+hop0a5g==", "dev": true, "license": "Apache-2.0" }, "node_modules/@prisma/driver-adapter-utils": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-6.19.0.tgz", - "integrity": "sha512-VAC/wFebV569Jk7iEqzLxekM2A5toKYAr6cPM2KWVHiRHgyjsh/IHf++Xo67q8uor/JxY8mwOuyQyuxkstSf5w==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.0.1.tgz", + "integrity": "sha512-sBbxm/yysHLLF2iMAB+qcX/nn3WFgsiC4DQNz0uM6BwGSIs8lIvgo0u8nR9nxe5gvFgKiIH8f4z2fgOEMeXc8w==", "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.19.0" + "@prisma/debug": "7.0.1" } }, - "node_modules/@prisma/driver-adapter-utils/node_modules/@prisma/debug": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.0.tgz", - "integrity": "sha512-8hAdGG7JmxrzFcTzXZajlQCidX0XNkMJkpqtfbLV54wC6LSSX6Vni25W/G+nAANwLnZ2TmwkfIuWetA7jJxJFA==", - "license": "Apache-2.0" - }, "node_modules/@prisma/engines": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.19.0.tgz", - "integrity": "sha512-pMRJ+1S6NVdXoB8QJAPIGpKZevFjxhKt0paCkRDTZiczKb7F4yTgRP8M4JdVkpQwmaD4EoJf6qA+p61godDokw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.0.1.tgz", + "integrity": "sha512-f+D/vdKeImqUHysd5Bgv8LQ1whl4sbLepHyYMQQMK61cp4WjwJVryophleLUrfEJRpBLGTBI/7fnLVENxxMFPQ==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.19.0", - "@prisma/engines-version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773", - "@prisma/fetch-engine": "6.19.0", - "@prisma/get-platform": "6.19.0" + "@prisma/debug": "7.0.1", + "@prisma/engines-version": "7.1.0-2.f09f2815f091dbba658cdcd2264306d88bb5bda6", + "@prisma/fetch-engine": "7.0.1", + "@prisma/get-platform": "7.0.1" } }, "node_modules/@prisma/engines-version": { - "version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773.tgz", - "integrity": "sha512-gV7uOBQfAFlWDvPJdQxMT1aSRur3a0EkU/6cfbAC5isV67tKDWUrPauyaHNpB+wN1ebM4A9jn/f4gH+3iHSYSQ==", + "version": "7.1.0-2.f09f2815f091dbba658cdcd2264306d88bb5bda6", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.1.0-2.f09f2815f091dbba658cdcd2264306d88bb5bda6.tgz", + "integrity": "sha512-RA7pShKvijHib4USRB3YuLTQamHKJPkTRDc45AwxfahUQngiGVMlIj4ix4emUxkrum4o/jwn82WIwlG57EtgiQ==", "devOptional": true, "license": "Apache-2.0" }, - "node_modules/@prisma/engines/node_modules/@prisma/debug": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.0.tgz", - "integrity": "sha512-8hAdGG7JmxrzFcTzXZajlQCidX0XNkMJkpqtfbLV54wC6LSSX6Vni25W/G+nAANwLnZ2TmwkfIuWetA7jJxJFA==", + "node_modules/@prisma/engines/node_modules/@prisma/get-platform": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.0.1.tgz", + "integrity": "sha512-DrsGnZOsF7PlAE7UtqmJenWti87RQtg7v9qW9alS71Pj0P6ZQV0RuzRQaql9dCWoo6qKAaF5U/L4kI826MmiZg==", "devOptional": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.0.1" + } }, "node_modules/@prisma/fetch-engine": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.19.0.tgz", - "integrity": "sha512-OOx2Lda0DGrZ1rodADT06ZGqHzr7HY7LNMaFE2Vp8dp146uJld58sRuasdX0OiwpHgl8SqDTUKHNUyzEq7pDdQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.0.1.tgz", + "integrity": "sha512-5DnSairYIYU7dcv/9pb1KCwIRHZfhVOd34855d01lUI5QdF9rdCkMywPQbBM67YP7iCgQoEZO0/COtOMpR4i9A==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.19.0", - "@prisma/engines-version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773", - "@prisma/get-platform": "6.19.0" + "@prisma/debug": "7.0.1", + "@prisma/engines-version": "7.1.0-2.f09f2815f091dbba658cdcd2264306d88bb5bda6", + "@prisma/get-platform": "7.0.1" } }, - "node_modules/@prisma/fetch-engine/node_modules/@prisma/debug": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.0.tgz", - "integrity": "sha512-8hAdGG7JmxrzFcTzXZajlQCidX0XNkMJkpqtfbLV54wC6LSSX6Vni25W/G+nAANwLnZ2TmwkfIuWetA7jJxJFA==", + "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.0.1.tgz", + "integrity": "sha512-DrsGnZOsF7PlAE7UtqmJenWti87RQtg7v9qW9alS71Pj0P6ZQV0RuzRQaql9dCWoo6qKAaF5U/L4kI826MmiZg==", "devOptional": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.0.1" + } }, "node_modules/@prisma/generator": { - "version": "6.17.1", - "resolved": "https://registry.npmjs.org/@prisma/generator/-/generator-6.17.1.tgz", - "integrity": "sha512-R4SIlAtMHlxwXYYHiyWtN+MLRGFF3Jy+LvqcfInbCiaZkoqyhU5kj8/aOu2OV2ydNkkN+Q8ft9Tnv5a/b4pqPg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/generator/-/generator-7.0.1.tgz", + "integrity": "sha512-d/rPH4p2hdZKg1kfWBAL+SLDGGbxl8I74dZv5+Fm/mpH0lWXRwQrtvpxrPaKbnJnCkyddUS+4SOl9WV2iBMH7w==", "dev": true, "license": "Apache-2.0" }, "node_modules/@prisma/generator-helper": { - "version": "6.17.1", - "resolved": "https://registry.npmjs.org/@prisma/generator-helper/-/generator-helper-6.17.1.tgz", - "integrity": "sha512-ONuUTGXCTUK9K//ronFg4xOEARv+tZKOo5uVSJg6tj2y90gpXQunPYyvR17gEoAQrZT17kC0ie60ecv8nulWyQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/generator-helper/-/generator-helper-7.0.1.tgz", + "integrity": "sha512-cCwQFw6Sfm74mKwq8haxCyOOgpRKJ7iFRnr71srT/+onz9oCAKzRBDcDAmnAOLiPvz/RW7qy3RVw3upovwt7lg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.17.1", - "@prisma/dmmf": "6.17.1", - "@prisma/generator": "6.17.1" + "@prisma/debug": "7.0.1", + "@prisma/dmmf": "7.0.1", + "@prisma/generator": "7.0.1" } }, "node_modules/@prisma/get-platform": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.19.0.tgz", - "integrity": "sha512-ym85WDO2yDhC3fIXHWYpG3kVMBA49cL1XD2GCsCF8xbwoy2OkDQY44gEbAt2X46IQ4Apq9H6g0Ex1iFfPqEkHA==", + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.8.2.tgz", + "integrity": "sha512-vXSxyUgX3vm1Q70QwzwkjeYfRryIvKno1SXbIqwSptKwqKzskINnDUcx85oX+ys6ooN2ATGSD0xN2UTfg6Zcow==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.19.0" + "@prisma/debug": "6.8.2" } }, "node_modules/@prisma/get-platform/node_modules/@prisma/debug": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.0.tgz", - "integrity": "sha512-8hAdGG7JmxrzFcTzXZajlQCidX0XNkMJkpqtfbLV54wC6LSSX6Vni25W/G+nAANwLnZ2TmwkfIuWetA7jJxJFA==", + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.8.2.tgz", + "integrity": "sha512-4muBSSUwJJ9BYth5N8tqts8JtiLT8QI/RSAzEogwEfpbYGFo9mYsInsVo8dqXdPO2+Rm5OG5q0qWDDE3nyUbVg==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/instrumentation": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-6.19.0.tgz", - "integrity": "sha512-QcuYy25pkXM8BJ37wVFBO7Zh34nyRV1GOb2n3lPkkbRYfl4hWl3PTcImP41P0KrzVXfa/45p6eVCos27x3exIg==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-7.0.1.tgz", + "integrity": "sha512-akYN1HvEARcV+bkutLJcsEw2MDnj1qP2A8b66KGsSDpAdesvGAy8bqyWKPHizzHD5brR5HPMC/cObUCIeMbg/w==", "license": "Apache-2.0", "dependencies": { - "@opentelemetry/instrumentation": ">=0.52.0 <1" + "@opentelemetry/instrumentation": "^0.207.0" }, "peerDependencies": { "@opentelemetry/api": "^1.8" } }, + "node_modules/@prisma/instrumentation/node_modules/@opentelemetry/api-logs": { + "version": "0.207.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.207.0.tgz", + "integrity": "sha512-lAb0jQRVyleQQGiuuvCOTDVspc14nx6XJjP4FspJ1sNARo3Regq4ZZbrc3rN4b1TYSuUCvgH+UXUPug4SLOqEQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@prisma/instrumentation/node_modules/@opentelemetry/instrumentation": { + "version": "0.207.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.207.0.tgz", + "integrity": "sha512-y6eeli9+TLKnznrR8AZlQMSJT7wILpXH+6EYq5Vf/4Ao+huI7EedxQHwRgVUOMLFbe7VFDvHJrX9/f4lcwnJsA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.207.0", + "import-in-the-middle": "^2.0.0", + "require-in-the-middle": "^8.0.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@prisma/instrumentation/node_modules/import-in-the-middle": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-2.0.0.tgz", + "integrity": "sha512-yNZhyQYqXpkT0AKq3F3KLasUSK4fHvebNH5hOsKQw2dhGSALvQ4U0BqUc5suziKvydO5u5hgN2hy1RJaho8U5A==", + "license": "Apache-2.0", + "dependencies": { + "acorn": "^8.14.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^1.2.2", + "module-details-from-path": "^1.0.3" + } + }, + "node_modules/@prisma/instrumentation/node_modules/require-in-the-middle": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", + "integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3" + }, + "engines": { + "node": ">=9.3.0 || >=8.10.0 <9.0.0" + } + }, + "node_modules/@prisma/query-plan-executor": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-6.18.0.tgz", + "integrity": "sha512-jZ8cfzFgL0jReE1R10gT8JLHtQxjWYLiQ//wHmVYZ2rVkFHoh0DT8IXsxcKcFlfKN7ak7k6j0XMNn2xVNyr5cA==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/studio-core": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.8.2.tgz", + "integrity": "sha512-/iAEWEUpTja+7gVMu1LtR2pPlvDmveAwMHdTWbDeGlT7yiv0ZTCPpmeAGdq/Y9aJ9Zj1cEGBXGRbmmNPj022PQ==", + "devOptional": true, + "license": "UNLICENSED", + "peerDependencies": { + "@types/react": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -4867,7 +5077,6 @@ "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", "license": "MIT", - "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -4978,7 +5187,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.14.tgz", "integrity": "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.8.0" } @@ -5015,6 +5223,25 @@ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "license": "MIT" }, + "node_modules/@types/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react/node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT", + "peer": true + }, "node_modules/@types/request": { "version": "2.48.12", "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", @@ -5191,7 +5418,6 @@ "integrity": "sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.40.0", "@typescript-eslint/types": "8.40.0", @@ -5358,7 +5584,6 @@ "integrity": "sha512-Cgzi2MXSZyAUOY+BFwGs17s7ad/7L+gKt6Y8rAVVWS+7o6wrjeFN4nVfTpbE25MNcxyJ+iYUXflbs2xR9h4UBg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.40.0", @@ -5906,7 +6131,6 @@ "integrity": "sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/utils": "3.2.4", "fflate": "^0.8.2", @@ -5978,7 +6202,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6019,7 +6242,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -6067,6 +6289,7 @@ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -6202,6 +6425,16 @@ "node": ">=8.0.0" } }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/axios": { "version": "0.21.4", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", @@ -6568,6 +6801,21 @@ "node": ">= 16" } }, + "node_modules/chevrotain": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-10.5.0.tgz", + "integrity": "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "10.5.0", + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "@chevrotain/utils": "10.5.0", + "lodash": "4.17.21", + "regexp-to-ast": "0.5.0" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -7061,7 +7309,6 @@ "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -7123,7 +7370,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -7270,6 +7517,16 @@ "node": ">=0.4.0" } }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -7604,7 +7861,6 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -7741,7 +7997,6 @@ "integrity": "sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/types": "^8.35.0", "comment-parser": "^1.4.1", @@ -7795,7 +8050,6 @@ "integrity": "sha512-P9s/qXSMTpRTerE2FQ0qJet2gKbcGyFTPAJipoKxmWqR6uuFqIqk8FuEfg5yBieOezVrEfAMZrEwJ6yEp+1MFQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/utils": "^6.0.0 || ^7.0.0 || ^8.0.0" }, @@ -8114,7 +8368,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -8271,9 +8524,9 @@ "license": "MIT" }, "node_modules/exsolve": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", - "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", "devOptional": true, "license": "MIT" }, @@ -8306,23 +8559,6 @@ "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==", - "devOptional": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, "node_modules/fast-copy": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", @@ -8563,7 +8799,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.6", @@ -8580,7 +8816,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, + "devOptional": true, "license": "ISC", "engines": { "node": ">=14" @@ -8707,6 +8943,16 @@ "node": ">=14" } }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -8942,9 +9188,16 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, + "devOptional": true, "license": "ISC" }, + "node_modules/grammex": { + "version": "3.1.12", + "resolved": "https://registry.npmjs.org/grammex/-/grammex-3.1.12.tgz", + "integrity": "sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -9029,6 +9282,16 @@ "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", "license": "MIT" }, + "node_modules/hono": { + "version": "4.7.10", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.7.10.tgz", + "integrity": "sha512-QkACju9MiN59CKSY5JsGZCYmPZkA6sIW6OFCUp7qDjZu6S6KHtJHhAc9Uy9mV9F8PJ1/HQ3ybZF2yjCa/73fvQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -9180,6 +9443,7 @@ "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.1.0.tgz", "integrity": "sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -9352,6 +9616,13 @@ "node": ">=8" } }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "devOptional": true, + "license": "MIT" + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -9400,7 +9671,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { @@ -9927,6 +10198,16 @@ "node": ">= 0.8.0" } }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -10085,6 +10366,32 @@ "dev": true, "license": "MIT" }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/lru.min": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.3.tgz", + "integrity": "sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q==", + "devOptional": true, + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, "node_modules/lunr": { "version": "2.3.9", "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", @@ -10326,7 +10633,6 @@ "integrity": "sha512-UczzB+0nnwGotYSgllfARAqWCJ5e/skuV2K/l+Zyck/H6pJIhLXuBnz+6vn2i211o7DtbE78HQtsYEKICHGI+g==", "dev": true, "license": "MIT", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/mobx" @@ -10469,6 +10775,57 @@ "url": "https://github.com/sponsors/raouldeheer" } }, + "node_modules/mysql2": { + "version": "3.15.3", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz", + "integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.7.0", + "long": "^5.2.1", + "lru.min": "^1.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/mysql2/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -10906,14 +11263,14 @@ "version": "12.1.3", "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/openapi-typescript": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-7.8.0.tgz", "integrity": "sha512-1EeVWmDzi16A+siQlo/SwSGIT7HwaFAVjvMA7/jG5HMLSnrUOzPL7uSTRZZa4v/LCRxHTApHKtNY6glApEoiUQ==", "license": "MIT", + "peer": true, "dependencies": { "@redocly/openapi-core": "^1.34.3", "ansi-colors": "^4.1.3", @@ -10934,6 +11291,7 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.26.2", "index-to-position": "^1.1.0", @@ -10951,6 +11309,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.1.0.tgz", "integrity": "sha512-GBuewsPrhJPftT+fqDa9oI/zc5HNsG9nREqwzoSFDOIqf0NggOZbHQj2TE1P1CDJK8ZogFnlZY9hWoUiur7I/A==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -11242,7 +11601,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -11344,7 +11703,6 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", "license": "MIT", - "peer": true, "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", @@ -11631,6 +11989,20 @@ "dev": true, "license": "MIT" }, + "node_modules/postgres": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz", + "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==", + "devOptional": true, + "license": "Unlicense", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/porsager" + } + }, "node_modules/postgres-array": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz", @@ -11685,7 +12057,6 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -11797,41 +12168,49 @@ "license": "0BSD" }, "node_modules/prisma": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.19.0.tgz", - "integrity": "sha512-F3eX7K+tWpkbhl3l4+VkFtrwJlLXbAM+f9jolgoUZbFcm1DgHZ4cq9AgVEgUym2au5Ad/TDLN8lg83D+M10ycw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.0.1.tgz", + "integrity": "sha512-zp93MdFMSU1IHPEXbUHVUuD8wauh2BUm14OVxhxGrWJQQpXpda0rW4VSST2bci4raoldX64/wQxHKkl/wqDskQ==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { - "@prisma/config": "6.19.0", - "@prisma/engines": "6.19.0" + "@prisma/config": "7.0.1", + "@prisma/dev": "0.13.0", + "@prisma/engines": "7.0.1", + "@prisma/studio-core": "0.8.2", + "mysql2": "3.15.3", + "postgres": "3.4.7" }, "bin": { "prisma": "build/index.js" }, "engines": { - "node": ">=18.18" + "node": "^20.19 || ^22.12 || >=24.0" }, "peerDependencies": { - "typescript": ">=5.1.0" + "better-sqlite3": ">=9.0.0", + "typescript": ">=5.4.0" }, "peerDependenciesMeta": { + "better-sqlite3": { + "optional": true + }, "typescript": { "optional": true } } }, "node_modules/prisma-json-types-generator": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prisma-json-types-generator/-/prisma-json-types-generator-3.6.2.tgz", - "integrity": "sha512-WX/oENQ0S74r/Wgd2uuHT5i3KbnwLFCP2Fq5ISzrXkus/htOC4uCaQPYuGP2m/wSeKZZCw1RxptTlD+ib7Ht/A==", + "version": "4.0.0-beta.1", + "resolved": "https://registry.npmjs.org/prisma-json-types-generator/-/prisma-json-types-generator-4.0.0-beta.1.tgz", + "integrity": "sha512-JUpTlZ6QGWRyU5+Iz4zAfRYalK4Z744VRvDG4Gb3pW4R1bi5NRRVtOT2N+CirOEm0Nj0zU3zu90r/0z6+gwR6g==", "dev": true, "license": "MIT", "dependencies": { - "@prisma/generator-helper": "^6.16.1", - "semver": "^7.7.2", + "@prisma/generator-helper": "^7.0.0", + "semver": "^7.7.3", + "try": "^1.0.1", "tslib": "^2.8.1" }, "bin": { @@ -11844,9 +12223,9 @@ "url": "https://github.com/arthurfiorette/prisma-json-types-generator?sponsor=1" }, "peerDependencies": { - "@prisma/client": "^6.14", - "prisma": "^6.14", - "typescript": "^5.9.2" + "@prisma/client": "^7.0.0", + "prisma": "^7.0.0", + "typescript": "^5.9.3" } }, "node_modules/prisma-json-types-generator/node_modules/tslib": { @@ -11894,7 +12273,6 @@ "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-15.1.3.tgz", "integrity": "sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@opentelemetry/api": "^1.4.0", "tdigest": "^0.1.1" @@ -11922,6 +12300,18 @@ "dev": true, "license": "MIT" }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, "node_modules/protobufjs": { "version": "7.5.3", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.3.tgz", @@ -11979,6 +12369,23 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -12080,9 +12487,8 @@ "version": "19.2.0", "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", - "dev": true, + "devOptional": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -12091,9 +12497,8 @@ "version": "19.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", - "dev": true, + "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -12214,6 +12619,23 @@ "url": "https://github.com/Mermade/oas-kit?sponsor=1" } }, + "node_modules/regexp-to-ast": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", + "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/remeda": { + "version": "2.21.3", + "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.21.3.tgz", + "integrity": "sha512-XXrZdLA10oEOQhLLzEJEiFFSKi21REGAkHdImIb4rt/XXy8ORGXh5HCcpUOsElfPNDb+X6TA/+wkh+p2KffYmg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "type-fest": "^4.39.1" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -12329,6 +12751,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -12505,7 +12937,7 @@ "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/search-params": { @@ -12581,6 +13013,12 @@ "node": ">= 0.8" } }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==", + "devOptional": true + }, "node_modules/serve-static": { "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", @@ -12620,7 +13058,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -12633,7 +13071,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -12778,6 +13216,13 @@ "dev": true, "license": "ISC" }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "devOptional": true, + "license": "ISC" + }, "node_modules/simple-websocket": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/simple-websocket/-/simple-websocket-9.1.0.tgz", @@ -12888,6 +13333,16 @@ "node": ">= 10.x" } }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -13080,7 +13535,6 @@ "integrity": "sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@emotion/is-prop-valid": "1.2.2", "@emotion/unitless": "0.8.1", @@ -13155,7 +13609,6 @@ "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.4.tgz", "integrity": "sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==", "license": "MIT", - "peer": true, "dependencies": { "methods": "^1.1.2", "superagent": "^10.2.3" @@ -13393,7 +13846,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -13469,6 +13921,16 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, + "node_modules/try": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/try/-/try-1.0.1.tgz", + "integrity": "sha512-3S6RIoraErJFhkNmlNyojU9YmaEs6M2qvAyy7lSb6PgSbiX5hOgyeLVsgwJ74lCSMLxjCggtgiaOJ4BtCV7LNA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/arthurfiorette/try?sponsor=1" + } + }, "node_modules/ts-algebra": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-1.2.2.tgz", @@ -13608,7 +14070,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13817,6 +14278,21 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/valibot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.1.0.tgz", + "integrity": "sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -13832,7 +14308,6 @@ "integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -13949,7 +14424,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -13992,7 +14466,6 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -14100,7 +14573,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -14327,6 +14800,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zeptomatch": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.0.2.tgz", + "integrity": "sha512-H33jtSKf8Ijtb5BW6wua3G5DhnFjbFML36eFu+VdOoVY4HD9e7ggjqdM6639B+L87rjnR6Y+XeRzBXZdy52B/g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "grammex": "^3.1.10" + } + }, "node_modules/zx": { "version": "8.8.5", "resolved": "https://registry.npmjs.org/zx/-/zx-8.8.5.tgz", diff --git a/package.json b/package.json index 419db6bd..4ac156bc 100644 --- a/package.json +++ b/package.json @@ -51,9 +51,9 @@ "@map-colonies/schemas": "^1.13.0", "@map-colonies/telemetry": "^10.1.0", "@opentelemetry/api": "^1.9.0", - "@prisma/adapter-pg": "^6.19.0", - "@prisma/client": "^6.19.0", - "@prisma/instrumentation": "^6.19.0", + "@prisma/adapter-pg": "^7.0.1", + "@prisma/client": "^7.0.1", + "@prisma/instrumentation": "^7.0.1", "compression": "^1.8.1", "date-fns": "^4.1.0", "express": "^4.21.2", @@ -101,14 +101,14 @@ "jest-openapi": "^0.14.2", "prettier": "^3.3.3", "pretty-quick": "^4.1.1", - "prisma": "^6.19.0", - "prisma-json-types-generator": "^3.6.2", + "prisma": "^7.0.1", + "prisma-json-types-generator": "^4.0.0-beta.1", "rimraf": "^6.1.0", "supertest": "^7.1.4", "tsc-alias": "^1.8.11", "type-fest": "^4.38.0", "typescript": "^5.9.3", - "zx": "^8.8.5", - "vitest": "^3.1.4" + "vitest": "^3.1.4", + "zx": "^8.8.5" } } diff --git a/prisma.config.ts b/prisma.config.ts new file mode 100644 index 00000000..8e031f53 --- /dev/null +++ b/prisma.config.ts @@ -0,0 +1,15 @@ +import path from 'node:path'; +import { defineConfig } from 'prisma/config'; + +// DATABASE_URL is only needed for Prisma migrations +// At runtime, the PrismaClient uses the adapter configured in createConnection.ts +// Use an obviously placeholder URL for client generation if DATABASE_URL is not present +const migrateUrl = process.env.DATABASE_URL ?? 'postgresql://prisma-migrations-only:not-a-real-connection@localhost:5432/dummy?schema=public'; + +export default defineConfig({ + earlyAccess: true, + schema: path.join('src', 'db', 'prisma', 'schema.prisma'), + migrate: { + url: migrateUrl, + }, +}); diff --git a/src/common/errors.ts b/src/common/errors.ts index 965a48dc..54c9b4bb 100644 --- a/src/common/errors.ts +++ b/src/common/errors.ts @@ -5,8 +5,20 @@ export const prismaKnownErrors = { recordNotFound: 'P2025', } as const; +// XState machine state names (uppercase) +type XStateMachineState = + | 'CREATED' + | 'PENDING' + | 'IN_PROGRESS' + | 'COMPLETED' + | 'FAILED' + | 'ABORTED' + | 'PAUSED' + | 'WAITING' + | 'RETRIED'; + export function illegalStatusTransitionErrorMessage( - currentStatus: JobOperationStatus | StageOperationStatus | TaskOperationStatus, + currentStatus: XStateMachineState | string, requiredStatus: JobOperationStatus | StageOperationStatus | TaskOperationStatus ): string { return `Illegal status transition from ${currentStatus} to ${requiredStatus}`; diff --git a/src/common/utils/statusMapping.ts b/src/common/utils/statusMapping.ts new file mode 100644 index 00000000..8114802f --- /dev/null +++ b/src/common/utils/statusMapping.ts @@ -0,0 +1,169 @@ +/** + * This module provides bidirectional mapping between Prisma enum values (database values) + * and API enum values (OpenAPI specification values). + * + * In Prisma 7, enum values now use the @map() database values (e.g., 'Pending', 'In-Progress') + * instead of the TypeScript enum keys (e.g., 'PENDING', 'IN_PROGRESS'). + * + * The API contract uses uppercase enum values to match the OpenAPI specification. + */ + +import { + JobOperationStatus as PrismaJobOperationStatus, + StageOperationStatus as PrismaStageOperationStatus, + TaskOperationStatus as PrismaTaskOperationStatus, + Priority as PrismaPriority, +} from '@prismaClient'; +import type { components } from '@src/openapi'; + +// API types from OpenAPI specification +type ApiJobOperationStatus = components['schemas']['jobOperationStatus']; +type ApiStageOperationStatus = components['schemas']['stageOperationStatusResponse']; +type ApiTaskOperationStatus = components['schemas']['taskOperationStatusResponse']; +type ApiPriority = components['schemas']['priority']; + +// Bidirectional mapping for JobOperationStatus +const jobStatusPrismaToApi: Record = { + [PrismaJobOperationStatus.PENDING]: 'PENDING', + [PrismaJobOperationStatus.IN_PROGRESS]: 'IN_PROGRESS', + [PrismaJobOperationStatus.COMPLETED]: 'COMPLETED', + [PrismaJobOperationStatus.FAILED]: 'FAILED', + [PrismaJobOperationStatus.ABORTED]: 'ABORTED', + [PrismaJobOperationStatus.CREATED]: 'CREATED', + [PrismaJobOperationStatus.PAUSED]: 'PAUSED', +}; + +const jobStatusApiToPrisma: Record = { + PENDING: PrismaJobOperationStatus.PENDING, + IN_PROGRESS: PrismaJobOperationStatus.IN_PROGRESS, + COMPLETED: PrismaJobOperationStatus.COMPLETED, + FAILED: PrismaJobOperationStatus.FAILED, + ABORTED: PrismaJobOperationStatus.ABORTED, + CREATED: PrismaJobOperationStatus.CREATED, + PAUSED: PrismaJobOperationStatus.PAUSED, +}; + +// Bidirectional mapping for StageOperationStatus +const stageStatusPrismaToApi: Record = { + [PrismaStageOperationStatus.PENDING]: 'PENDING', + [PrismaStageOperationStatus.IN_PROGRESS]: 'IN_PROGRESS', + [PrismaStageOperationStatus.COMPLETED]: 'COMPLETED', + [PrismaStageOperationStatus.FAILED]: 'FAILED', + [PrismaStageOperationStatus.ABORTED]: 'ABORTED', + [PrismaStageOperationStatus.WAITING]: 'WAITING', + [PrismaStageOperationStatus.CREATED]: 'CREATED', +}; + +const stageStatusApiToPrisma: Record = { + PENDING: PrismaStageOperationStatus.PENDING, + IN_PROGRESS: PrismaStageOperationStatus.IN_PROGRESS, + COMPLETED: PrismaStageOperationStatus.COMPLETED, + FAILED: PrismaStageOperationStatus.FAILED, + ABORTED: PrismaStageOperationStatus.ABORTED, + WAITING: PrismaStageOperationStatus.WAITING, + CREATED: PrismaStageOperationStatus.CREATED, +}; + +// Bidirectional mapping for TaskOperationStatus +const taskStatusPrismaToApi: Record = { + [PrismaTaskOperationStatus.PENDING]: 'PENDING', + [PrismaTaskOperationStatus.IN_PROGRESS]: 'IN_PROGRESS', + [PrismaTaskOperationStatus.COMPLETED]: 'COMPLETED', + [PrismaTaskOperationStatus.FAILED]: 'FAILED', + [PrismaTaskOperationStatus.CREATED]: 'CREATED', + [PrismaTaskOperationStatus.RETRIED]: 'RETRIED', +}; + +const taskStatusApiToPrisma: Record = { + PENDING: PrismaTaskOperationStatus.PENDING, + IN_PROGRESS: PrismaTaskOperationStatus.IN_PROGRESS, + COMPLETED: PrismaTaskOperationStatus.COMPLETED, + FAILED: PrismaTaskOperationStatus.FAILED, + CREATED: PrismaTaskOperationStatus.CREATED, + RETRIED: PrismaTaskOperationStatus.RETRIED, +}; + +// Bidirectional mapping for Priority +const priorityPrismaToApi: Record = { + [PrismaPriority.VERY_HIGH]: 'VERY_HIGH', + [PrismaPriority.HIGH]: 'HIGH', + [PrismaPriority.MEDIUM]: 'MEDIUM', + [PrismaPriority.LOW]: 'LOW', + [PrismaPriority.VERY_LOW]: 'VERY_LOW', +}; + +const priorityApiToPrisma: Record = { + VERY_HIGH: PrismaPriority.VERY_HIGH, + HIGH: PrismaPriority.HIGH, + MEDIUM: PrismaPriority.MEDIUM, + LOW: PrismaPriority.LOW, + VERY_LOW: PrismaPriority.VERY_LOW, +}; + +// Conversion functions with runtime validation +export function convertJobStatusToApi(prismaStatus: PrismaJobOperationStatus): ApiJobOperationStatus { + const result = jobStatusPrismaToApi[prismaStatus]; + if (result === undefined) { + throw new Error(`Unknown Prisma job status: ${prismaStatus}`); + } + return result; +} + +export function convertJobStatusToPrisma(apiStatus: ApiJobOperationStatus): PrismaJobOperationStatus { + const result = jobStatusApiToPrisma[apiStatus]; + if (result === undefined) { + throw new Error(`Unknown API job status: ${apiStatus}`); + } + return result; +} + +export function convertStageStatusToApi(prismaStatus: PrismaStageOperationStatus): ApiStageOperationStatus { + const result = stageStatusPrismaToApi[prismaStatus]; + if (result === undefined) { + throw new Error(`Unknown Prisma stage status: ${prismaStatus}`); + } + return result; +} + +export function convertStageStatusToPrisma(apiStatus: ApiStageOperationStatus): PrismaStageOperationStatus { + const result = stageStatusApiToPrisma[apiStatus]; + if (result === undefined) { + throw new Error(`Unknown API stage status: ${apiStatus}`); + } + return result; +} + +export function convertTaskStatusToApi(prismaStatus: PrismaTaskOperationStatus): ApiTaskOperationStatus { + const result = taskStatusPrismaToApi[prismaStatus]; + if (result === undefined) { + throw new Error(`Unknown Prisma task status: ${prismaStatus}`); + } + return result; +} + +export function convertTaskStatusToPrisma(apiStatus: ApiTaskOperationStatus): PrismaTaskOperationStatus { + const result = taskStatusApiToPrisma[apiStatus]; + if (result === undefined) { + throw new Error(`Unknown API task status: ${apiStatus}`); + } + return result; +} + +export function convertPriorityToApi(prismaPriority: PrismaPriority): ApiPriority { + const result = priorityPrismaToApi[prismaPriority]; + if (result === undefined) { + throw new Error(`Unknown Prisma priority: ${prismaPriority}`); + } + return result; +} + +export function convertPriorityToPrisma(apiPriority: ApiPriority): PrismaPriority { + const result = priorityApiToPrisma[apiPriority]; + if (result === undefined) { + throw new Error(`Unknown API priority: ${apiPriority}`); + } + return result; +} + +// Export types for use in other modules +export type { ApiJobOperationStatus, ApiStageOperationStatus, ApiTaskOperationStatus, ApiPriority }; diff --git a/src/db/prisma/schema.prisma b/src/db/prisma/schema.prisma index 981f7fbc..49edab2c 100644 --- a/src/db/prisma/schema.prisma +++ b/src/db/prisma/schema.prisma @@ -10,7 +10,6 @@ generator json { datasource db { provider = "postgresql" - url = env("DATABASE_URL") } model Job { diff --git a/src/jobs/models/manager.ts b/src/jobs/models/manager.ts index 128bccae..35cd2e19 100644 --- a/src/jobs/models/manager.ts +++ b/src/jobs/models/manager.ts @@ -5,7 +5,7 @@ import type { Tracer } from '@opentelemetry/api'; import { trace } from '@opentelemetry/api'; import { withSpanAsyncV4 } from '@map-colonies/telemetry'; import { INFRA_CONVENTIONS } from '@map-colonies/telemetry/conventions'; -import type { PrismaClient, Priority } from '@prismaClient'; +import type { PrismaClient } from '@prismaClient'; import { Prisma, JobOperationStatus } from '@prismaClient'; import { SERVICES } from '@common/constants'; import { convertArrayPrismaStageToStageResponse } from '@src/stages/models/helper'; @@ -14,6 +14,14 @@ import { type PrismaTransaction } from '@src/db/types'; import { resolveTraceContext } from '@src/common/utils/tracingHelpers'; import { IllegalJobStatusTransitionError, JobNotInFiniteStateError, JobNotFoundError } from '@src/common/generated/errors'; import { ATTR_MESSAGING_MESSAGE_CONVERSATION_ID } from '@src/common/semconv'; +import { + convertJobStatusToApi, + convertJobStatusToPrisma, + convertPriorityToApi, + convertPriorityToPrisma, + type ApiJobOperationStatus, + type ApiPriority, +} from '@common/utils/statusMapping'; import { errorMessages as jobsErrorMessages, SamePriorityChangeError } from './errors'; import type { JobCreateModel, JobModel, JobFindCriteriaArg, JobPrismaObject } from './models'; import { jobStateMachine, OperationStatusMapper } from './jobStateMachine'; @@ -31,11 +39,14 @@ export class JobManager { let queryBody = undefined; if (params !== undefined) { + // Convert API priority to Prisma priority if provided + const prismaPriority = params.priority ? convertPriorityToPrisma(params.priority) : undefined; + queryBody = { where: { AND: { name: { equals: params.job_name }, - priority: { equals: params.priority }, + priority: { equals: prismaPriority }, creationTime: { gte: params.from_date, lte: params.end_date }, }, }, @@ -64,13 +75,19 @@ export class JobManager { const { traceparent, tracestate } = resolveTraceContext(body); - const input = { - ...body, + // Convert API priority to Prisma priority if provided + const prismaPriority = body.priority ? convertPriorityToPrisma(body.priority) : undefined; + + const input: Prisma.JobCreateInput = { + name: body.name, + data: body.data, + userMetadata: body.userMetadata, + priority: prismaPriority, status: JobOperationStatus.PENDING, xstate: persistenceSnapshot, traceparent, tracestate, - } satisfies Prisma.JobCreateInput; + }; const createdJob = await this.prisma.job.create({ data: input, include: { stage: false } }); const res = this.convertPrismaToJobResponse(createdJob); @@ -127,10 +144,10 @@ export class JobManager { } @withSpanAsyncV4 - public async updatePriority(jobId: string, priority: Priority): Promise { + public async updatePriority(jobId: string, apiPriority: ApiPriority): Promise { trace.getActiveSpan()?.setAttributes({ [ATTR_MESSAGING_MESSAGE_CONVERSATION_ID]: jobId, - [INFRA_CONVENTIONS.infra.jobnik.job.priority]: priority, + [INFRA_CONVENTIONS.infra.jobnik.job.priority]: apiPriority, }); const job = await this.getJobEntityById(jobId); @@ -139,7 +156,10 @@ export class JobManager { throw new JobNotFoundError(jobsErrorMessages.jobNotFound); } - if (job.priority === priority) { + // Convert API priority to Prisma priority for comparison + const prismaPriority = convertPriorityToPrisma(apiPriority); + + if (job.priority === prismaPriority) { throw new SamePriorityChangeError(jobsErrorMessages.priorityCannotBeUpdatedToSameValue); } @@ -148,7 +168,7 @@ export class JobManager { id: jobId, }, data: { - priority, + priority: prismaPriority, }, }; @@ -156,10 +176,10 @@ export class JobManager { } @withSpanAsyncV4 - public async updateStatus(jobId: string, status: JobOperationStatus, tx?: PrismaTransaction): Promise { + public async updateStatus(jobId: string, apiStatus: ApiJobOperationStatus, tx?: PrismaTransaction): Promise { trace.getActiveSpan()?.setAttributes({ [ATTR_MESSAGING_MESSAGE_CONVERSATION_ID]: jobId, - [INFRA_CONVENTIONS.infra.jobnik.job.status]: status, + [INFRA_CONVENTIONS.infra.jobnik.job.status]: apiStatus, }); const prisma = tx ?? this.prisma; @@ -170,12 +190,14 @@ export class JobManager { throw new JobNotFoundError(jobsErrorMessages.jobNotFound); } - const nextStatusChange = OperationStatusMapper[status]; + // Convert API status to Prisma status + const prismaStatus = convertJobStatusToPrisma(apiStatus); + const nextStatusChange = OperationStatusMapper[prismaStatus]; const updateActor = createActor(jobStateMachine, { snapshot: job.xstate }).start(); const isValidStatus = updateActor.getSnapshot().can({ type: nextStatusChange }); if (!isValidStatus) { - throw new IllegalJobStatusTransitionError(illegalStatusTransitionErrorMessage(job.status, status)); + throw new IllegalJobStatusTransitionError(illegalStatusTransitionErrorMessage(updateActor.getSnapshot().value as string, prismaStatus)); } updateActor.send({ type: nextStatusChange }); @@ -186,7 +208,7 @@ export class JobManager { id: jobId, }, data: { - status, + status: prismaStatus, xstate: newPersistedSnapshot, }, }; @@ -258,7 +280,7 @@ export class JobManager { private convertPrismaToJobResponse(prismaObjects: JobPrismaObject): JobModel; private convertPrismaToJobResponse(prismaObjects: JobPrismaObject): JobModel; private convertPrismaToJobResponse(prismaObjects: JobPrismaObject): JobModel { - const { data, creationTime, userMetadata, updateTime, tracestate, xstate, stage, ...rest } = prismaObjects; + const { data, creationTime, userMetadata, updateTime, tracestate, xstate, stage, status, priority, ...rest } = prismaObjects; const transformedFields = { data: data as Record, @@ -267,6 +289,8 @@ export class JobManager { updateTime: updateTime.toISOString(), tracestate: tracestate ?? undefined, stages: Array.isArray(stage) ? convertArrayPrismaStageToStageResponse(stage) : undefined, + status: convertJobStatusToApi(status), + priority: convertPriorityToApi(priority), }; return Object.assign(rest, transformedFields); diff --git a/src/stages/DAL/stageRepository.ts b/src/stages/DAL/stageRepository.ts index eab34b3a..2f562141 100644 --- a/src/stages/DAL/stageRepository.ts +++ b/src/stages/DAL/stageRepository.ts @@ -4,7 +4,7 @@ import { Prisma, PrismaClient } from '@prismaClient'; import { SERVICES } from '@src/common/constants'; import { PrismaTransaction } from '@src/db/types'; import { StageSummary, UpdateSummaryCount } from '../models/models'; -import { summaryCountsMapper, taskOperationStatusWithTotal } from '../models/helper'; +import { summaryCountsMapper, apiTaskOperationStatusWithTotal, getSummaryKeyForStatus } from '../models/helper'; @scoped(Lifecycle.ContainerScoped) export class StageRepository { @@ -14,14 +14,14 @@ export class StageRepository { ) {} public async updateStageSummary(stageId: string, summaryPayload: UpdateSummaryCount, tx: PrismaTransaction): Promise { - const addStatus = summaryCountsMapper[summaryPayload.add.status]; + const addStatus = getSummaryKeyForStatus(summaryPayload.add.status); const addCount = summaryPayload.add.count; this.logger.debug({ msg: `Updating stage summary`, stageId, summaryPayload }); let setClause; // Construct the 'remove' update if it exists if (summaryPayload.remove) { - const removeStatus = summaryCountsMapper[summaryPayload.remove.status]; + const removeStatus = getSummaryKeyForStatus(summaryPayload.remove.status); const removeCount = summaryPayload.remove.count; setClause = Prisma.sql`summary = summary || jsonb_build_object( @@ -32,7 +32,7 @@ export class StageRepository { )`; } else { // Construct the 'add' update (update also the total count) - const totalCount = summaryCountsMapper[taskOperationStatusWithTotal.TOTAL]; + const totalCount = summaryCountsMapper[apiTaskOperationStatusWithTotal.TOTAL]; setClause = Prisma.sql`summary = summary || jsonb_build_object( ${addStatus}::text, ("summary"->>${addStatus}::text)::integer + ${addCount}, diff --git a/src/stages/models/helper.ts b/src/stages/models/helper.ts index 57f00d02..34f3c58f 100644 --- a/src/stages/models/helper.ts +++ b/src/stages/models/helper.ts @@ -2,6 +2,7 @@ import { createActor } from 'xstate'; import { TaskOperationStatus } from '@prismaClient'; import { convertArrayPrismaTaskToTaskResponse } from '@src/tasks/models/helper'; import { createCamelCaseMapper } from '@src/common/utils/formatter'; +import { convertStageStatusToApi, type ApiTaskOperationStatus } from '@common/utils/statusMapping'; import { StageCreateModel, StageModel, StagePrismaObject, StageSummary } from './models'; import { stageStateMachine } from './stageStateMachine'; @@ -11,15 +12,48 @@ import { stageStateMachine } from './stageStateMachine'; */ type StagePersistedSnapshot = ReturnType>['getPersistedSnapshot']>; -const taskOperationStatusWithTotal = { - ...TaskOperationStatus, - // eslint-disable-next-line @typescript-eslint/naming-convention +// API task status values for summary counts (uppercase to match OpenAPI) +const apiTaskOperationStatusWithTotal = { + PENDING: 'PENDING', + IN_PROGRESS: 'IN_PROGRESS', + COMPLETED: 'COMPLETED', + FAILED: 'FAILED', + CREATED: 'CREATED', + RETRIED: 'RETRIED', TOTAL: 'TOTAL', } as const; -const summaryCountsMapper = createCamelCaseMapper(taskOperationStatusWithTotal); +const summaryCountsMapper = createCamelCaseMapper(apiTaskOperationStatusWithTotal); // eslint-disable-next-line @typescript-eslint/naming-convention type SummaryCountsMapper = typeof summaryCountsMapper & { TOTAL: 'total' }; +// Map from Prisma TaskOperationStatus to summary key +const prismaStatusToSummaryKey: Record = { + [TaskOperationStatus.PENDING]: 'PENDING', + [TaskOperationStatus.IN_PROGRESS]: 'IN_PROGRESS', + [TaskOperationStatus.COMPLETED]: 'COMPLETED', + [TaskOperationStatus.FAILED]: 'FAILED', + [TaskOperationStatus.CREATED]: 'CREATED', + [TaskOperationStatus.RETRIED]: 'RETRIED', +}; + +/** + * Converts a Prisma TaskOperationStatus to the corresponding summary count key + * @param status Prisma TaskOperationStatus + * @returns The summary count key (camelCase) + * @throws Error if the status is not recognized + */ +function getSummaryKeyForStatus(status: TaskOperationStatus): string { + const mappedKey = prismaStatusToSummaryKey[status]; + if (mappedKey === undefined) { + throw new Error(`Unknown task status: ${status}`); + } + const summaryKey = summaryCountsMapper[mappedKey]; + if (summaryKey === undefined) { + throw new Error(`No summary key mapping for status: ${status}`); + } + return summaryKey; +} + const defaultStatusCounts = Object.fromEntries(Object.values(summaryCountsMapper).map((value) => [value, 0])) as Record< SummaryCountsMapper[keyof SummaryCountsMapper], 0 @@ -31,13 +65,14 @@ const defaultStatusCounts = Object.fromEntries(Object.values(summaryCountsMapper * @returns StageModel */ function convertPrismaToStageResponse(prismaObjects: StagePrismaObject): StageModel { - const { data, userMetadata, task, tracestate, xstate, ...rest } = prismaObjects; + const { data, userMetadata, task, tracestate, xstate, status, ...rest } = prismaObjects; const transformedFields = { data: data as Record, userMetadata: userMetadata as Record, tasks: Array.isArray(task) ? convertArrayPrismaTaskToTaskResponse(task) : undefined, tracestate: tracestate ?? undefined, + status: convertStageStatusToApi(status), }; return Object.assign(rest, transformedFields); } @@ -93,7 +128,8 @@ export { getInitialXstate, summaryCountsMapper, defaultStatusCounts, - taskOperationStatusWithTotal, + apiTaskOperationStatusWithTotal, + getSummaryKeyForStatus, }; export type { StagePersistedSnapshot }; diff --git a/src/stages/models/manager.ts b/src/stages/models/manager.ts index 63e721c0..92b1cc1c 100644 --- a/src/stages/models/manager.ts +++ b/src/stages/models/manager.ts @@ -16,6 +16,12 @@ import { IllegalStageStatusTransitionError, JobInFiniteStateError, JobNotFoundEr import { errorMessages as stagesErrorMessages } from '@src/stages/models/errors'; import type { PrismaTransaction } from '@src/db/types'; import { ATTR_MESSAGING_DESTINATION_NAME, ATTR_MESSAGING_MESSAGE_CONVERSATION_ID } from '@src/common/semconv'; +import { + convertStageStatusToPrisma, + convertStageStatusToApi, + convertJobStatusToApi, + type ApiStageOperationStatus, +} from '@common/utils/statusMapping'; import { StageRepository } from '../DAL/stageRepository'; import type { StageCreateModel, @@ -130,12 +136,17 @@ export class StageManager { public async getStages(params: StageFindCriteriaArg): Promise { let queryBody = undefined; if (params !== undefined) { + // Convert API status to Prisma status if provided + const prismaStatus = params.stage_operation_status + ? convertStageStatusToPrisma(params.stage_operation_status as ApiStageOperationStatus) + : undefined; + queryBody = { where: { AND: { jobId: { equals: params.job_id }, type: { equals: params.stage_type }, - status: { equals: params.stage_operation_status }, + status: { equals: prismaStatus }, }, }, include: { task: params.should_return_tasks }, @@ -234,11 +245,11 @@ export class StageManager { } @withSpanAsyncV4 - public async updateStatus(stageId: string, status: StageOperationStatus, tx?: PrismaTransaction): Promise { + public async updateStatus(stageId: string, apiStatus: ApiStageOperationStatus, tx?: PrismaTransaction): Promise { const spanActive = trace.getActiveSpan(); spanActive?.setAttributes({ [INFRA_CONVENTIONS.infra.jobnik.stage.id]: stageId, - [INFRA_CONVENTIONS.infra.jobnik.stage.status]: status, + [INFRA_CONVENTIONS.infra.jobnik.stage.status]: apiStatus, }); const prisma = tx ?? this.prisma; @@ -248,11 +259,15 @@ export class StageManager { if (!stage) { throw new StageNotFoundError(stagesErrorMessages.stageNotFound); } + + // Convert API status to Prisma status + const prismaStatus = convertStageStatusToPrisma(apiStatus); + //#region validate status transition rules const previousStageOrder = stage.order - 1; // can't move to PENDING if previous stage is not COMPLETED - if (status === StageOperationStatus.PENDING && previousStageOrder > 0) { + if (prismaStatus === StageOperationStatus.PENDING && previousStageOrder > 0) { const previousStage = await prisma.stage.findFirst({ where: { jobId: stage.jobId, @@ -265,12 +280,12 @@ export class StageManager { } } - const nextStatusChange = OperationStatusMapper[status]; + const nextStatusChange = OperationStatusMapper[prismaStatus]; const updateActor = createActor(stageStateMachine, { snapshot: stage.xstate }).start(); const isValidStatus = updateActor.getSnapshot().can({ type: nextStatusChange }); if (!isValidStatus) { - throw new IllegalStageStatusTransitionError(illegalStatusTransitionErrorMessage(stage.status, status)); + throw new IllegalStageStatusTransitionError(illegalStatusTransitionErrorMessage(updateActor.getSnapshot().value as string, prismaStatus)); } //#endregion updateActor.send({ type: nextStatusChange }); @@ -281,7 +296,7 @@ export class StageManager { id: stageId, }, data: { - status, + status: prismaStatus, xstate: newPersistedSnapshot, }, }; @@ -291,7 +306,7 @@ export class StageManager { //#region update related entities // Update job completion when a stage is completed // If the stage is marked as completed, and there is a next stage in the job, update the next stage status to PENDING - if (status === StageOperationStatus.COMPLETED) { + if (prismaStatus === StageOperationStatus.COMPLETED) { const nextStageOrder = stage.order + 1; const nextStage = await prisma.stage.findFirst({ where: { @@ -301,13 +316,15 @@ export class StageManager { }); if (nextStage && nextStage.status === StageOperationStatus.CREATED) { - await this.updateStatus(nextStage.id, StageOperationStatus.PENDING, tx); + // Convert Prisma status to API status for internal call + await this.updateStatus(nextStage.id, convertStageStatusToApi(StageOperationStatus.PENDING), tx); trace.getActiveSpan()?.addEvent('Next stage set to PENDING', { nextStageId: nextStage.id }); } const { completedStages, totalStages } = await this.updateJobCompletionProgress(stage.jobId, tx); if (completedStages === totalStages) { - await this.jobManager.updateStatus(stage.jobId, JobOperationStatus.COMPLETED, tx); + // Convert Prisma status to API status for cross-manager call + await this.jobManager.updateStatus(stage.jobId, convertJobStatusToApi(JobOperationStatus.COMPLETED), tx); this.logger.info({ msg: 'Job completed as all stages are done', jobId: stage.jobId, @@ -317,13 +334,13 @@ export class StageManager { } } - if (status === StageOperationStatus.IN_PROGRESS && stage.job.status === JobOperationStatus.PENDING) { + if (prismaStatus === StageOperationStatus.IN_PROGRESS && stage.job.status === JobOperationStatus.PENDING) { // Update job status to IN_PROGRESS - await this.jobManager.updateStatus(stage.job.id, JobOperationStatus.IN_PROGRESS, tx); + await this.jobManager.updateStatus(stage.job.id, convertJobStatusToApi(JobOperationStatus.IN_PROGRESS), tx); trace.getActiveSpan()?.addEvent('Job status set to IN_PROGRESS because first stage is being processed', { jobId: stage.jobId }); - } else if (status === StageOperationStatus.FAILED) { + } else if (prismaStatus === StageOperationStatus.FAILED) { // Update job status to FAILED - await this.jobManager.updateStatus(stage.jobId, JobOperationStatus.FAILED, tx); + await this.jobManager.updateStatus(stage.jobId, convertJobStatusToApi(JobOperationStatus.FAILED), tx); trace.getActiveSpan()?.addEvent('Job set to FAILED because its stage failed', { jobId: stage.jobId }); } @@ -385,7 +402,8 @@ export class StageManager { // update stage status if it was initialized by first task // and the stage is not already in progress if (updatedSummary.inProgress > 0 && stage.status === StageOperationStatus.PENDING) { - await this.updateStatus(stageId, StageOperationStatus.IN_PROGRESS, tx); + // Convert Prisma status to API status for internal call + await this.updateStatus(stageId, convertStageStatusToApi(StageOperationStatus.IN_PROGRESS), tx); trace.getActiveSpan()?.addEvent('Stage set to IN_PROGRESS because first task started', { stageId }); } } @@ -411,7 +429,8 @@ export class StageManager { await tx.stage.update({ where: { id: stage.id }, data: stageUpdatedData }); if (summary.total === summary.completed) { - await this.updateStatus(stage.id, StageOperationStatus.COMPLETED, tx); + // Convert Prisma status to API status for internal call + await this.updateStatus(stage.id, convertStageStatusToApi(StageOperationStatus.COMPLETED), tx); this.logger.info({ msg: 'Stage completed, updating job progress', diff --git a/src/tasks/models/helper.ts b/src/tasks/models/helper.ts index fe37913b..aaf78ce4 100644 --- a/src/tasks/models/helper.ts +++ b/src/tasks/models/helper.ts @@ -1,4 +1,5 @@ import { Prisma } from '@prismaClient'; +import { convertTaskStatusToApi } from '@common/utils/statusMapping'; import { TaskModel } from './models'; /** @@ -7,7 +8,7 @@ import { TaskModel } from './models'; * @returns TaskModel */ export function convertPrismaToTaskResponse(prismaObjects: Prisma.TaskGetPayload>): TaskModel { - const { data, userMetadata, xstate, creationTime, tracestate, startTime, endTime, updateTime, ...rest } = prismaObjects; + const { data, userMetadata, xstate, creationTime, tracestate, startTime, endTime, updateTime, status, ...rest } = prismaObjects; const transformedFields = { data: data as Record, @@ -17,6 +18,7 @@ export function convertPrismaToTaskResponse(prismaObjects: Prisma.TaskGetPayload tracestate: tracestate ?? undefined, startTime: startTime ? startTime.toISOString() : undefined, endTime: endTime ? endTime.toISOString() : undefined, + status: convertTaskStatusToApi(status), }; return Object.assign(rest, transformedFields); diff --git a/src/tasks/models/manager.ts b/src/tasks/models/manager.ts index 0f279726..2bbc1152 100644 --- a/src/tasks/models/manager.ts +++ b/src/tasks/models/manager.ts @@ -16,6 +16,12 @@ import { stageStateMachine } from '@src/stages/models/stageStateMachine'; import { type ConfigType } from '@src/common/config'; import type { UpdateSummaryCount } from '@src/stages/models/models'; import type { PrismaTransaction } from '@src/db/types'; +import { + convertTaskStatusToPrisma, + convertTaskStatusToApi, + convertStageStatusToApi, + type ApiTaskOperationStatus, +} from '@common/utils/statusMapping'; import { NotAllowedToAddTasksToInProgressStageError, StageInFiniteStateError, @@ -113,7 +119,9 @@ export class TaskManager { throw new StageInFiniteStateError(stagesErrorMessages.stageAlreadyFinishedTasksError); } - if (checkStageStatus.getSnapshot().value === StageOperationStatus.IN_PROGRESS) { + // XState machine state values are uppercase (e.g., 'IN_PROGRESS') + // which match API status values, not Prisma database values + if (checkStageStatus.getSnapshot().value === 'IN_PROGRESS') { this.logger.error(`Failed adding tasks to stage, not allowed on running stage`); throw new NotAllowedToAddTasksToInProgressStageError(tasksErrorMessages.addTaskNotAllowed); } @@ -167,6 +175,9 @@ export class TaskManager { public async getTasks(params: TasksFindCriteriaArg): Promise { const hasNoParams = params === undefined || Object.keys(params).length === 0; + // Convert API status to Prisma status if provided + const prismaStatus = params?.status ? convertTaskStatusToPrisma(params.status as ApiTaskOperationStatus) : undefined; + const queryBody: Prisma.TaskFindManyArgs = { where: hasNoParams ? undefined @@ -174,7 +185,7 @@ export class TaskManager { AND: { stageId: { equals: params.stage_id }, stage: { type: { equals: params.stage_type } }, - status: { equals: params.status }, + status: { equals: prismaStatus }, creationTime: { gte: params.from_date, lte: params.end_date }, }, }, @@ -246,11 +257,11 @@ export class TaskManager { } @withSpanAsyncV4 - public async updateStatus(taskId: string, status: TaskOperationStatus): Promise { + public async updateStatus(taskId: string, apiStatus: ApiTaskOperationStatus): Promise { const spanActive = trace.getActiveSpan(); spanActive?.setAttributes({ [ATTR_MESSAGING_MESSAGE_ID]: taskId, - [INFRA_CONVENTIONS.infra.jobnik.stage.status]: status, + [INFRA_CONVENTIONS.infra.jobnik.stage.status]: apiStatus, }); const task = await this.getTaskEntityById(taskId); @@ -258,7 +269,10 @@ export class TaskManager { if (!task) { throw new TaskNotFoundError(tasksErrorMessages.taskNotFound); } - const updatedTask = await this.updateAndValidateStatus(task, status); + + // Convert API status to Prisma status + const prismaStatus = convertTaskStatusToPrisma(apiStatus); + const updatedTask = await this.updateAndValidateStatus(task, prismaStatus); return convertPrismaToTaskResponse(updatedTask); } @@ -431,7 +445,8 @@ export class TaskManager { stageId: task.stageId, }); - await this.stageManager.updateStatus(task.stageId, StageOperationStatus.FAILED, tx); + // Convert Prisma status to API status for cross-manager call + await this.stageManager.updateStatus(task.stageId, convertStageStatusToApi(StageOperationStatus.FAILED), tx); trace.getActiveSpan()?.addEvent('Stage set to FAILED', { stageId: task.stageId }); } @@ -526,7 +541,8 @@ export class TaskManager { // Process tasks sequentially to avoid overwhelming the database for (const task of staleTasks) { try { - await this.updateStatus(task.id, TaskOperationStatus.FAILED); + // Convert Prisma status to API status for internal call + await this.updateStatus(task.id, convertTaskStatusToApi(TaskOperationStatus.FAILED)); successCount++; this.logger.debug({ diff --git a/tests/unit/jobs/jobs.spec.ts b/tests/unit/jobs/jobs.spec.ts index 37940b58..d3eaad58 100644 --- a/tests/unit/jobs/jobs.spec.ts +++ b/tests/unit/jobs/jobs.spec.ts @@ -8,10 +8,11 @@ import { errorMessages as jobsErrorMessages } from '@src/jobs/models/errors'; import { JobCreateModel } from '@src/jobs/models/models'; import { randomUuid } from '@tests/unit/generator'; import { SERVICE_NAME } from '@src/common/constants'; +import { createMockPrismaClient } from '@tests/unit/mocks/prismaClientMock'; import { jobEntityWithAbortStatus, jobEntityWithoutStages, jobEntityWithStages } from '../data'; let jobManager: JobManager; -const prisma = new PrismaClient(); +const prisma = createMockPrismaClient(); const tracer = trace.getTracer(SERVICE_NAME); const jobNotFoundError = new Prisma.PrismaClientKnownRequestError('RECORD_NOT_FOUND', { code: prismaKnownErrors.recordNotFound, clientVersion: '1' }); @@ -61,12 +62,16 @@ describe('JobManager', () => { const mediumPriorityJob = { ...jobEntityWithoutStages, priority: Priority.MEDIUM }; vi.spyOn(prisma.job, 'findMany').mockResolvedValue([mediumPriorityJob]); - const jobs = await jobManager.getJobs({ priority: Priority.MEDIUM }); + // Use API priority value + const jobs = await jobManager.getJobs({ priority: 'MEDIUM' }); - const { xstate, stage, ...rest } = mediumPriorityJob; + const { xstate, stage, status, priority, ...rest } = mediumPriorityJob; + // Status and priority are converted to API values const expectedJob = [ { ...rest, + status: 'PENDING', + priority: 'MEDIUM', stages: stage, creationTime: rest.creationTime.toISOString(), updateTime: rest.updateTime.toISOString(), @@ -83,8 +88,18 @@ describe('JobManager', () => { const jobs = await jobManager.getJobs(undefined); - const { xstate, stage, tracestate, ...rest } = jobEntity; - const expectedJob = [{ ...rest, stages: stage, creationTime: rest.creationTime.toISOString(), updateTime: rest.updateTime.toISOString() }]; + const { xstate, stage, tracestate, status, priority, ...rest } = jobEntity; + // Status and priority are converted to API values + const expectedJob = [ + { + ...rest, + status: 'PENDING', + priority: 'HIGH', + stages: stage, + creationTime: rest.creationTime.toISOString(), + updateTime: rest.updateTime.toISOString(), + }, + ]; expect(jobs).toMatchObject(expectedJob); }); @@ -94,7 +109,8 @@ describe('JobManager', () => { it('should fail with a database error when finding jobs', async function () { vi.spyOn(prisma.job, 'findMany').mockRejectedValueOnce(new Error('db connection error')); - await expect(jobManager.getJobs({ priority: Priority.MEDIUM })).rejects.toThrow('db connection error'); + // Use API priority value + await expect(jobManager.getJobs({ priority: 'MEDIUM' })).rejects.toThrow('db connection error'); }); }); }); @@ -106,9 +122,12 @@ describe('JobManager', () => { const jobs = await jobManager.getJobById(jobEntityWithoutStages.id); - const { xstate, stage, ...rest } = jobEntityWithoutStages; + const { xstate, stage, status, priority, ...rest } = jobEntityWithoutStages; + // Status and priority are converted from Prisma values to API values const expectedJob = { ...rest, + status: 'PENDING', // API value + priority: 'HIGH', // API value stages: stage, tracestate: undefined, creationTime: rest.creationTime.toISOString(), @@ -168,13 +187,15 @@ describe('JobManager', () => { vi.spyOn(prisma.job, 'findUnique').mockResolvedValue(jobEntityWithoutStages); vi.spyOn(prisma.job, 'update').mockResolvedValue(jobEntityWithoutStages); - await expect(jobManager.updatePriority(jobEntityWithoutStages.id, Priority.MEDIUM)).toResolve(); + // Use API priority value (uppercase) + await expect(jobManager.updatePriority(jobEntityWithoutStages.id, 'MEDIUM')).toResolve(); }); it('should not perform a job priority update when the provided priority matches the current job priority', async function () { vi.spyOn(prisma.job, 'findUnique').mockResolvedValue({ ...jobEntityWithoutStages, priority: Priority.HIGH }); - await expect(jobManager.updatePriority(jobEntityWithoutStages.id, Priority.HIGH)).rejects.toThrow( + // Use API priority value - should throw because job already has HIGH priority (Prisma: 'High') + await expect(jobManager.updatePriority(jobEntityWithoutStages.id, 'HIGH')).rejects.toThrow( jobsErrorMessages.priorityCannotBeUpdatedToSameValue ); }); @@ -184,7 +205,8 @@ describe('JobManager', () => { it('should fail when updating priority of a non-existent job', async function () { vi.spyOn(prisma.job, 'findUnique').mockResolvedValue(null); - await expect(jobManager.updatePriority('someId', Priority.MEDIUM)).rejects.toThrow(jobsErrorMessages.jobNotFound); + // Use API priority value + await expect(jobManager.updatePriority('someId', 'MEDIUM')).rejects.toThrow(jobsErrorMessages.jobNotFound); }); }); @@ -192,7 +214,8 @@ describe('JobManager', () => { it('should fail with a database error when updating priority', async function () { vi.spyOn(prisma.job, 'findUnique').mockRejectedValueOnce(new Error('db connection error')); - await expect(jobManager.updatePriority('someId', Priority.MEDIUM)).rejects.toThrow('db connection error'); + // Use API priority value + await expect(jobManager.updatePriority('someId', 'MEDIUM')).rejects.toThrow('db connection error'); }); }); }); @@ -205,7 +228,8 @@ describe('JobManager', () => { vi.spyOn(prisma.job, 'findUnique').mockResolvedValue({ ...jobEntityWithoutStages, id: jobId }); vi.spyOn(prisma.job, 'update').mockResolvedValue(jobEntityWithoutStages); - await expect(jobManager.updateStatus(jobId, JobOperationStatus.PENDING)).toResolve(); + // Use API status value + await expect(jobManager.updateStatus(jobId, 'PENDING')).toResolve(); }); }); @@ -213,14 +237,17 @@ describe('JobManager', () => { it('should fail when updating status for a job that does not exist', async function () { vi.spyOn(prisma.job, 'findUnique').mockResolvedValue(null); - await expect(jobManager.updateStatus('someId', JobOperationStatus.PENDING)).rejects.toThrow(jobsErrorMessages.jobNotFound); + // Use API status value + await expect(jobManager.updateStatus('someId', 'PENDING')).rejects.toThrow(jobsErrorMessages.jobNotFound); }); it('should fail on invalid status transition', async function () { vi.spyOn(prisma.job, 'findUnique').mockResolvedValue(jobEntityWithoutStages); - await expect(jobManager.updateStatus(jobEntityWithoutStages.id, JobOperationStatus.COMPLETED)).rejects.toThrow( - illegalStatusTransitionErrorMessage(jobEntityWithoutStages.status, JobOperationStatus.COMPLETED) + // XState machine initial state is 'CREATED' (uppercase), Prisma status is 'Completed' + // Use API status value + await expect(jobManager.updateStatus(jobEntityWithoutStages.id, 'COMPLETED')).rejects.toThrow( + illegalStatusTransitionErrorMessage('CREATED', JobOperationStatus.COMPLETED) ); }); }); @@ -229,7 +256,8 @@ describe('JobManager', () => { it('should fail with a database error when updating status', async function () { vi.spyOn(prisma.job, 'findUnique').mockRejectedValueOnce(new Error('db connection error')); - await expect(jobManager.updateStatus('someId', JobOperationStatus.COMPLETED)).rejects.toThrow('db connection error'); + // Use API status value + await expect(jobManager.updateStatus('someId', 'COMPLETED')).rejects.toThrow('db connection error'); }); }); }); diff --git a/tests/unit/mocks/prismaClientMock.ts b/tests/unit/mocks/prismaClientMock.ts new file mode 100644 index 00000000..eb7eb89c --- /dev/null +++ b/tests/unit/mocks/prismaClientMock.ts @@ -0,0 +1,71 @@ +/** + * Mock PrismaClient for unit tests. + * + * In Prisma 7, PrismaClient requires an adapter to be instantiated. + * For unit tests that mock database operations, we create a mock object + * that has the same interface as PrismaClient without actually connecting. + */ +import type { PrismaClient } from '@prismaClient'; +import { vi } from 'vitest'; + +export function createMockPrismaClient(): PrismaClient { + return { + $connect: vi.fn(), + $disconnect: vi.fn(), + $executeRaw: vi.fn(), + $executeRawUnsafe: vi.fn(), + $queryRaw: vi.fn(), + $queryRawUnsafe: vi.fn(), + $transaction: vi.fn(), + $extends: vi.fn(), + job: { + findMany: vi.fn(), + findUnique: vi.fn(), + findFirst: vi.fn(), + create: vi.fn(), + createMany: vi.fn(), + update: vi.fn(), + updateMany: vi.fn(), + delete: vi.fn(), + deleteMany: vi.fn(), + count: vi.fn(), + aggregate: vi.fn(), + groupBy: vi.fn(), + upsert: vi.fn(), + }, + stage: { + findMany: vi.fn(), + findUnique: vi.fn(), + findFirst: vi.fn(), + create: vi.fn(), + createMany: vi.fn(), + createManyAndReturn: vi.fn(), + update: vi.fn(), + updateMany: vi.fn(), + updateManyAndReturn: vi.fn(), + delete: vi.fn(), + deleteMany: vi.fn(), + count: vi.fn(), + aggregate: vi.fn(), + groupBy: vi.fn(), + upsert: vi.fn(), + }, + task: { + findMany: vi.fn(), + findUnique: vi.fn(), + findFirst: vi.fn(), + create: vi.fn(), + createMany: vi.fn(), + createManyAndReturn: vi.fn(), + update: vi.fn(), + updateMany: vi.fn(), + updateManyAndReturn: vi.fn(), + delete: vi.fn(), + deleteMany: vi.fn(), + count: vi.fn(), + aggregate: vi.fn(), + groupBy: vi.fn(), + upsert: vi.fn(), + }, + } as unknown as PrismaClient; +} diff --git a/tests/unit/stages/repository.spec.ts b/tests/unit/stages/repository.spec.ts index d2aeea76..c5da8415 100644 --- a/tests/unit/stages/repository.spec.ts +++ b/tests/unit/stages/repository.spec.ts @@ -5,9 +5,10 @@ import { PrismaClient, TaskOperationStatus } from '@prismaClient'; import { UpdateSummaryCount } from '@src/stages/models/models'; import { defaultStatusCounts } from '@src/stages/models/helper'; import { StageRepository } from '@src/stages/DAL/stageRepository'; +import { createMockPrismaClient } from '@tests/unit/mocks/prismaClientMock'; import { createStageEntity } from '../generator'; -const prisma = new PrismaClient(); +const prisma = createMockPrismaClient(); let stageRepository: StageRepository; describe('JobManager', () => { diff --git a/tests/unit/stages/stages.spec.ts b/tests/unit/stages/stages.spec.ts index 7c66b861..340d2497 100644 --- a/tests/unit/stages/stages.spec.ts +++ b/tests/unit/stages/stages.spec.ts @@ -15,6 +15,7 @@ import { StageRepository } from '@src/stages/DAL/stageRepository'; import { JobPrismaObject } from '@src/jobs/models/models'; import { SERVICE_NAME } from '@src/common/constants'; import { JobInFiniteStateError } from '@src/common/generated/errors'; +import { createMockPrismaClient } from '@tests/unit/mocks/prismaClientMock'; import { completedStageXstatePersistentSnapshot, inProgressStageXstatePersistentSnapshot, @@ -30,7 +31,7 @@ let jobManager: JobManager; let stageManager: StageManager; let stageRepository: StageRepository; const tracer = trace.getTracer(SERVICE_NAME); -const prisma = new PrismaClient(); +const prisma = createMockPrismaClient(); type StageAggregateResult = Prisma.GetStageAggregateType; const notFoundError = new Prisma.PrismaClientKnownRequestError('RECORD_NOT_FOUND', { code: prismaKnownErrors.recordNotFound, clientVersion: '1' }); @@ -55,9 +56,10 @@ describe('JobManager', () => { vi.spyOn(prisma.stage, 'findMany').mockResolvedValue([stageEntity]); const stages = await stageManager.getStages({ stage_type: 'SOME_STAGE_TYPE' }); - const { xstate, task, tracestate, ...rest } = stageEntity; + const { xstate, task, tracestate, status, ...rest } = stageEntity; - const expectedStage = [rest]; + // Status is converted from Prisma value to API value + const expectedStage = [{ ...rest, status: 'CREATED' }]; expect(stages).toMatchObject(expectedStage); expect(stages[0]?.tasks).toBeUndefined(); @@ -70,9 +72,10 @@ describe('JobManager', () => { vi.spyOn(prisma.stage, 'findMany').mockResolvedValue([stageEntity]); const stages = await stageManager.getStages({ stage_type: 'SOME_STAGE_TYPE', should_return_tasks: true }); - const { xstate, task, tracestate, ...rest } = stageEntity; + const { xstate, task, tracestate, status, ...rest } = stageEntity; - const expectedStage = [rest]; + // Status is converted from Prisma value to API value + const expectedStage = [{ ...rest, status: 'CREATED' }]; expect(stages).toMatchObject(expectedStage); expect(stages[0]?.tasks).toMatchObject([{ id: taskEntity.id }]); @@ -83,9 +86,9 @@ describe('JobManager', () => { vi.spyOn(prisma.stage, 'findMany').mockResolvedValue([stageEntity]); const stages = await stageManager.getStages(undefined); - const { xstate, task, tracestate, ...rest } = stageEntity; + const { xstate, task, tracestate, status, ...rest } = stageEntity; - const expectedStage = [rest]; + const expectedStage = [{ ...rest, status: 'CREATED' }]; expect(stages).toMatchObject(expectedStage); }); @@ -111,9 +114,9 @@ describe('JobManager', () => { const stage = await stageManager.getStageById(stageId); - const { xstate, task, tracestate, ...rest } = stageEntity; + const { xstate, task, tracestate, status, ...rest } = stageEntity; - const expectedStage = rest; + const expectedStage = { ...rest, status: 'CREATED' }; expect(stage).toMatchObject(expectedStage); expect(stage.tasks).toBeUndefined(); @@ -128,9 +131,9 @@ describe('JobManager', () => { const stage = await stageManager.getStageById(stageId); - const { xstate, task, tracestate, ...rest } = stageEntity; + const { xstate, task, tracestate, status, ...rest } = stageEntity; - const expectedStage = rest; + const expectedStage = { ...rest, status: 'CREATED' }; expect(stage).toMatchObject(expectedStage); expect(stage.tasks).toMatchObject([{ id: taskEntity.id }]); @@ -162,8 +165,8 @@ describe('JobManager', () => { const stage = await stageManager.getStagesByJobId(stageEntity.jobId); - const { xstate, task, tracestate, ...rest } = stageEntity; - const expectedStage = [rest]; + const { xstate, task, tracestate, status, ...rest } = stageEntity; + const expectedStage = [{ ...rest, status: 'CREATED' }]; expect(stage).toMatchObject(expectedStage); expect(stage[0]?.tasks).toBeUndefined(); @@ -179,9 +182,9 @@ describe('JobManager', () => { const stage = await stageManager.getStagesByJobId(stageEntity.jobId); - const { xstate, task, tracestate, ...rest } = stageEntity; + const { xstate, task, tracestate, status, ...rest } = stageEntity; - const expectedStage = [rest]; + const expectedStage = [{ ...rest, status: 'CREATED' }]; expect(stage).toMatchObject(expectedStage); expect(stage[0]?.tasks).toMatchObject([{ id: taskEntity.id }]); @@ -313,9 +316,9 @@ describe('JobManager', () => { const stagesResponse = await stageManager.addStage(uniqueJobId, anotherStagePayload); // Extract unnecessary fields from the stage object and assemble the expected result - const { xstate, task, tracestate, ...rest } = anotherStageEntity; - - expect(stagesResponse).toMatchObject(rest); + const { xstate, task, tracestate, status, ...rest } = anotherStageEntity; + // Status is converted from Prisma value to API value + expect(stagesResponse).toMatchObject({ ...rest, status: 'CREATED' }); }); it('should add stage with WAITING status when startAsWaiting flag is true', async function () { @@ -346,9 +349,9 @@ describe('JobManager', () => { const stagesResponse = await stageManager.addStage(uniqueJobId, anotherStagePayload); // Extract unnecessary fields from the stage object and assemble the expected result - const { xstate, task, tracestate, ...rest } = anotherStageEntity; - - expect(stagesResponse).toMatchObject(rest); + const { xstate, task, tracestate, status, ...rest } = anotherStageEntity; + // Status is converted from Prisma value to API value + expect(stagesResponse).toMatchObject({ ...rest, status: 'CREATED' }); }); it('should assign order 1 to the first stage in a job (internal logic)', async function () { @@ -507,7 +510,7 @@ describe('JobManager', () => { vi.spyOn(prisma.stage, 'findUnique').mockResolvedValue(stageEntityResult); vi.spyOn(prisma.stage, 'update').mockResolvedValue({ ...stageEntity, id: stageId }); - await expect(stageManager.updateStatus(stageEntity.id, StageOperationStatus.PENDING)).toResolve(); + await expect(stageManager.updateStatus(stageEntity.id, 'PENDING')).toResolve(); }); it('should successfully update next ordered stage status by id (CREATED -> PENDING)', async function () { @@ -523,7 +526,7 @@ describe('JobManager', () => { vi.spyOn(prisma.stage, 'update').mockResolvedValue({ ...stageEntity, id: stageId }); vi.spyOn(prisma.stage, 'findFirst').mockResolvedValue({ ...stageEntity, status: StageOperationStatus.COMPLETED }); - await expect(stageManager.updateStatus(stageEntity.id, StageOperationStatus.PENDING)).toResolve(); + await expect(stageManager.updateStatus(stageEntity.id, 'PENDING')).toResolve(); }); it('should successfully update next ordered stage status to pending after completion of current', async function () { @@ -573,7 +576,7 @@ describe('JobManager', () => { }); vi.spyOn(prisma.job, 'update').mockResolvedValue(jobEntityWithStages); - await expect(stageManager.updateStatus(stageId, StageOperationStatus.COMPLETED)).toResolve(); + await expect(stageManager.updateStatus(stageId, 'COMPLETED')).toResolve(); }); it('should successfully complete the final stage and also complete the job', async function () { @@ -602,7 +605,7 @@ describe('JobManager', () => { status: JobOperationStatus.IN_PROGRESS, xstate: inProgressStageXstatePersistentSnapshot, }); - await expect(stageManager.updateStatus(stageEntity.id, StageOperationStatus.COMPLETED)).toResolve(); + await expect(stageManager.updateStatus(stageEntity.id, 'COMPLETED')).toResolve(); }); it("should successfully complete stage and also update in-progress job's percentage", async function () { @@ -641,7 +644,7 @@ describe('JobManager', () => { updateSpy.mockResolvedValueOnce(stageEntityOrder2); vi.spyOn(prisma.job, 'update').mockResolvedValue(jobEntityWithStages); - await expect(stageManager.updateStatus(stageEntity.id, StageOperationStatus.COMPLETED)).toResolve(); + await expect(stageManager.updateStatus(stageEntity.id, 'COMPLETED')).toResolve(); }); it('should successfully update stage to IN_PROGRESS and move also the PENDING job to IN_PROGRESS', async function () { @@ -663,7 +666,7 @@ describe('JobManager', () => { }); vi.spyOn(prisma.job, 'update').mockResolvedValue(jobEntityWithStages); - await expect(stageManager.updateStatus(stageEntity.id, StageOperationStatus.IN_PROGRESS)).toResolve(); + await expect(stageManager.updateStatus(stageEntity.id, 'IN_PROGRESS')).toResolve(); }); }); @@ -671,7 +674,8 @@ describe('JobManager', () => { it('should fail when updating status for a state that does not exist', async function () { vi.spyOn(prisma.stage, 'findUnique').mockResolvedValue(null); - await expect(stageManager.updateStatus('someId', StageOperationStatus.PENDING)).rejects.toThrow(stagesErrorMessages.stageNotFound); + // Use API status value + await expect(stageManager.updateStatus('someId', 'PENDING')).rejects.toThrow(stagesErrorMessages.stageNotFound); }); it('should fail when updating status for a stage before previous completed', async function () { @@ -688,7 +692,8 @@ describe('JobManager', () => { vi.spyOn(prisma.stage, 'findUnique').mockResolvedValue(stageEntityResult); vi.spyOn(prisma.stage, 'findFirst').mockResolvedValue({ ...stageEntity, status: StageOperationStatus.IN_PROGRESS }); - await expect(stageManager.updateStatus(stageId, StageOperationStatus.PENDING)).rejects.toThrow('Previous stage is not COMPLETED'); + // Use API status value + await expect(stageManager.updateStatus(stageId, 'PENDING')).rejects.toThrow(`Previous stage is not ${StageOperationStatus.COMPLETED}`); }); it('should fail on invalid status transition', async function () { @@ -697,8 +702,9 @@ describe('JobManager', () => { job: { status: JobOperationStatus.IN_PROGRESS }, } as unknown as StageWithTasks); - await expect(stageManager.updateStatus(stageEntity.id, StageOperationStatus.COMPLETED)).rejects.toThrow( - illegalStatusTransitionErrorMessage(stageEntity.status, StageOperationStatus.COMPLETED) + // XState machine state is 'CREATED' (uppercase), Prisma status is 'Completed' + await expect(stageManager.updateStatus(stageEntity.id, 'COMPLETED')).rejects.toThrow( + illegalStatusTransitionErrorMessage('CREATED', StageOperationStatus.COMPLETED) ); }); }); @@ -707,7 +713,7 @@ describe('JobManager', () => { it('should fail with a database error when updating status', async function () { vi.spyOn(prisma.stage, 'findUnique').mockRejectedValueOnce(new Error('db connection error')); - await expect(stageManager.updateStatus('someId', StageOperationStatus.COMPLETED)).rejects.toThrow('db connection error'); + await expect(stageManager.updateStatus('someId', 'COMPLETED')).rejects.toThrow('db connection error'); }); }); }); diff --git a/tests/unit/tasks/tasks.spec.ts b/tests/unit/tasks/tasks.spec.ts index f8dfddf4..c4bf8e88 100644 --- a/tests/unit/tasks/tasks.spec.ts +++ b/tests/unit/tasks/tasks.spec.ts @@ -17,6 +17,7 @@ import { SERVICE_NAME } from '@src/common/constants'; import { IllegalTaskStatusTransitionError, NotAllowedToAddTasksToInProgressStageError, StageInFiniteStateError } from '@src/common/generated/errors'; import { getConfig, initConfig } from '@src/common/config'; import { DEFAULT_TRACEPARENT } from '@src/common/utils/tracingHelpers'; +import { createMockPrismaClient } from '@tests/unit/mocks/prismaClientMock'; import { createJobEntity, createStageEntity, createTaskEntity } from '../generator'; import { abortedStageXstatePersistentSnapshot, inProgressStageXstatePersistentSnapshot, pendingStageXstatePersistentSnapshot } from '../data'; @@ -41,7 +42,7 @@ let taskManager: TaskManager; let stageRepository: StageRepository; const tracer = trace.getTracer(SERVICE_NAME); -const prisma = new PrismaClient(); +const prisma = createMockPrismaClient(); let config: ReturnType; @@ -74,8 +75,11 @@ describe('JobManager', () => { const tasks = await taskManager.getTasks({ stage_type: 'SOME_STAGE_TYPE' }); - const { creationTime, updateTime, xstate, startTime, endTime, ...rest } = taskEntity; - const expectedTask = [{ ...rest, tracestate: undefined, creationTime: creationTime.toISOString(), updateTime: updateTime.toISOString() }]; + const { creationTime, updateTime, xstate, startTime, endTime, status, ...rest } = taskEntity; + // Status is converted from Prisma value (e.g. 'Created') to API value (e.g. 'CREATED') + const expectedTask = [ + { ...rest, status: 'CREATED', tracestate: undefined, creationTime: creationTime.toISOString(), updateTime: updateTime.toISOString() }, + ]; expect(tasks).toMatchObject(expectedTask); }); @@ -86,8 +90,11 @@ describe('JobManager', () => { const tasks = await taskManager.getTasks({}); - const { creationTime, updateTime, xstate, startTime, endTime, ...rest } = taskEntity; - const expectedTask = [{ ...rest, tracestate: undefined, creationTime: creationTime.toISOString(), updateTime: updateTime.toISOString() }]; + const { creationTime, updateTime, xstate, startTime, endTime, status, ...rest } = taskEntity; + // Status is converted from Prisma value (e.g. 'Created') to API value (e.g. 'CREATED') + const expectedTask = [ + { ...rest, status: 'CREATED', tracestate: undefined, creationTime: creationTime.toISOString(), updateTime: updateTime.toISOString() }, + ]; expect(tasks).toMatchObject(expectedTask); }); @@ -119,8 +126,15 @@ describe('JobManager', () => { const task = await taskManager.getTaskById(taskId); - const { creationTime, updateTime, xstate, startTime, endTime, ...rest } = taskEntity; - const expectedTask = { ...rest, tracestate: undefined, creationTime: creationTime.toISOString(), updateTime: updateTime.toISOString() }; + const { creationTime, updateTime, xstate, startTime, endTime, status, ...rest } = taskEntity; + // Status is converted from Prisma value (e.g. 'Created') to API value (e.g. 'CREATED') + const expectedTask = { + ...rest, + status: 'CREATED', + tracestate: undefined, + creationTime: creationTime.toISOString(), + updateTime: updateTime.toISOString(), + }; expect(task).toMatchObject(expectedTask); }); @@ -154,8 +168,11 @@ describe('JobManager', () => { const tasks = await taskManager.getTasksByStageId(stageEntity.id); - const { creationTime, updateTime, xstate, startTime, endTime, ...rest } = taskEntity; - const expectedTask = [{ ...rest, tracestate: undefined, creationTime: creationTime.toISOString(), updateTime: updateTime.toISOString() }]; + const { creationTime, updateTime, xstate, startTime, endTime, status, ...rest } = taskEntity; + // Status is converted from Prisma value (e.g. 'Created') to API value (e.g. 'CREATED') + const expectedTask = [ + { ...rest, status: 'CREATED', tracestate: undefined, creationTime: creationTime.toISOString(), updateTime: updateTime.toISOString() }, + ]; expect(tasks).toMatchObject(expectedTask); }); @@ -238,10 +255,11 @@ describe('JobManager', () => { const tasksResponse = await taskManager.addTasks(stageId, [taskPayload]); // Extract unnecessary fields from the job object and assemble the expected result - const { creationTime, updateTime, xstate, startTime, endTime, ...rest } = taskEntity; + const { creationTime, updateTime, xstate, startTime, endTime, status, ...rest } = taskEntity; + // Status is converted from Prisma value (e.g. 'Created') to API value (e.g. 'CREATED') expect(tasksResponse).toMatchObject([ - { ...rest, tracestate: undefined, creationTime: creationTime.toISOString(), updateTime: updateTime.toISOString() }, + { ...rest, status: 'CREATED', tracestate: undefined, creationTime: creationTime.toISOString(), updateTime: updateTime.toISOString() }, ]); }); }); @@ -346,7 +364,8 @@ describe('JobManager', () => { vi.spyOn(stageManager, 'updateStageProgressFromTaskChanges').mockResolvedValue(undefined); - await expect(taskManager.updateStatus(taskId, TaskOperationStatus.COMPLETED)).toResolve(); + // Use API status value (uppercase) + await expect(taskManager.updateStatus(taskId, 'COMPLETED')).toResolve(); }); it('should update task status to RETRIED', async function () { @@ -381,7 +400,7 @@ describe('JobManager', () => { vi.spyOn(stageManager, 'updateStageProgressFromTaskChanges').mockResolvedValue(undefined); - await expect(taskManager.updateStatus(taskId, TaskOperationStatus.FAILED)).toResolve(); + await expect(taskManager.updateStatus(taskId, 'FAILED')).toResolve(); }); it('should update task status to IN_PROGRESS and add startTime', async function () { @@ -416,7 +435,7 @@ describe('JobManager', () => { vi.spyOn(stageManager, 'updateStageProgressFromTaskChanges').mockResolvedValue(undefined); - await expect(taskManager.updateStatus(taskId, TaskOperationStatus.IN_PROGRESS)).toResolve(); + await expect(taskManager.updateStatus(taskId, 'IN_PROGRESS')).toResolve(); }); it('should update task status to FAILED and add endTime', async function () { @@ -462,7 +481,7 @@ describe('JobManager', () => { vi.spyOn(stageManager, 'updateStageProgressFromTaskChanges').mockResolvedValue(undefined); - await expect(taskManager.updateStatus(taskId, TaskOperationStatus.FAILED)).toResolve(); + await expect(taskManager.updateStatus(taskId, 'FAILED')).toResolve(); }); it('should update task status to IN_PROGRESS', async function () { @@ -497,7 +516,7 @@ describe('JobManager', () => { vi.spyOn(stageManager, 'updateStageProgressFromTaskChanges').mockResolvedValue(undefined); - await expect(taskManager.updateStatus(taskId, TaskOperationStatus.IN_PROGRESS)).toResolve(); + await expect(taskManager.updateStatus(taskId, 'IN_PROGRESS')).toResolve(); }); }); @@ -505,7 +524,7 @@ describe('JobManager', () => { it('should reject changing status on a non-existent task', async function () { vi.spyOn(prisma.task, 'findUnique').mockResolvedValue(null); - await expect(taskManager.updateStatus('someId', TaskOperationStatus.COMPLETED)).rejects.toThrow(tasksErrorMessages.taskNotFound); + await expect(taskManager.updateStatus('someId', 'COMPLETED')).rejects.toThrow(tasksErrorMessages.taskNotFound); }); it("should reject update invalid task's status [from IN_PROGRESS to CREATED]", async function () { @@ -527,7 +546,7 @@ describe('JobManager', () => { const mockTx = {} as unknown as Omit; return callback(mockTx); }); - await expect(taskManager.updateStatus(taskId, TaskOperationStatus.CREATED)).rejects.toThrow(IllegalTaskStatusTransitionError); + await expect(taskManager.updateStatus(taskId, 'CREATED')).rejects.toThrow(IllegalTaskStatusTransitionError); }); }); @@ -627,7 +646,7 @@ describe('JobManager', () => { vi.spyOn(prisma.task, 'findMany').mockResolvedValue([staleTaskOneHour, staleTaskFortyFiveMinutes]); const taskManagerUpdatesStatusMock = vi.spyOn(taskManager, 'updateStatus').mockResolvedValue({ id: staleTaskOneHour.id, - status: TaskOperationStatus.FAILED, + status: 'FAILED', // API status value attempts: 0, data: {}, maxAttempts: 2, @@ -641,8 +660,9 @@ describe('JobManager', () => { await expect(taskManager.cleanStaleTasks()).toResolve(); expect(taskManagerUpdatesStatusMock).toHaveBeenCalledTimes(2); - expect(taskManagerUpdatesStatusMock).toHaveBeenNthCalledWith(1, staleTaskOneHour.id, TaskOperationStatus.FAILED); - expect(taskManagerUpdatesStatusMock).toHaveBeenNthCalledWith(2, staleTaskFortyFiveMinutes.id, TaskOperationStatus.FAILED); + // updateStatus is now called with API status values + expect(taskManagerUpdatesStatusMock).toHaveBeenNthCalledWith(1, staleTaskOneHour.id, 'FAILED'); + expect(taskManagerUpdatesStatusMock).toHaveBeenNthCalledWith(2, staleTaskFortyFiveMinutes.id, 'FAILED'); }); it('should handle empty result when no stale tasks are found', async () => {