From 951953cedbfac8f53b4a5b1876915af7dc4bde80 Mon Sep 17 00:00:00 2001 From: "hash-worker[bot]" <180894564+hash-worker[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 00:44:39 +0000 Subject: [PATCH 01/24] Update `clap` Rust crates to v4.5.23 (#5814) Co-authored-by: hash-worker[bot] <180894564+hash-worker[bot]@users.noreply.github.com> --- Cargo.lock | 12 ++++++------ Cargo.toml | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f4a650934e6..a74cf97b7aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1270,9 +1270,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.22" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69371e34337c4c984bbe322360c2547210bf632eb2814bbe78a6e87a2935bd2b" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", "clap_derive", @@ -1280,9 +1280,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.22" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e24c1b4099818523236a8ca881d2b45db98dadfb4625cf6608c12069fcbbde1" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstream", "anstyle", @@ -1314,9 +1314,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "color_quant" diff --git a/Cargo.toml b/Cargo.toml index 8488e40760c..3607f1906ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,7 +95,7 @@ axum = { version = "0.7.5" } axum-core = { version = "0.4.3" } bumpalo = { version = "=3.16.0", default-features = false } bytes = { version = "1.6.0" } -clap_builder = { version = "=4.5.22", default-features = false, features = ["std"] } +clap_builder = { version = "=4.5.23", default-features = false, features = ["std"] } criterion = { version = "=0.5.1" } deadpool = { version = "=0.12.1", default-features = false } deadpool-postgres = { version = "=0.14.0", default-features = false } @@ -158,7 +158,7 @@ aws-config = { version = "=1.5.10" } aws-sdk-s3 = { version = "=1.65.0", default-features = false } bitvec = { version = "=1.0.1", default-features = false } bytes-utils = { version = "=0.1.4", default-features = false } -clap = { version = "=4.5.22", features = ["color", "error-context", "help", "std", "suggestions", "usage"] } +clap = { version = "=4.5.23", features = ["color", "error-context", "help", "std", "suggestions", "usage"] } clap_complete = { version = "=4.5.38", default-features = false } convert_case = { version = "=0.6.0", default-features = false } criterion-macro = { version = "=0.4.0", default-features = false } From 4c6bbee59cb0029be13c8c4ee8a126ee4d64cfa3 Mon Sep 17 00:00:00 2001 From: "hash-worker[bot]" <180894564+hash-worker[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 00:48:03 +0000 Subject: [PATCH 02/24] Update effect npm packages (#5815) Co-authored-by: hash-worker[bot] <180894564+hash-worker[bot]@users.noreply.github.com> --- .../harpc/client/typescript/package.json | 6 +-- yarn.lock | 50 +++++++++---------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/libs/@local/harpc/client/typescript/package.json b/libs/@local/harpc/client/typescript/package.json index 3e2540f0791..ebc08aa75d9 100644 --- a/libs/@local/harpc/client/typescript/package.json +++ b/libs/@local/harpc/client/typescript/package.json @@ -27,15 +27,15 @@ "@libp2p/ping": "2.0.12", "@libp2p/tcp": "10.0.13", "@multiformats/multiaddr": "12.3.4", - "effect": "3.11.2", + "effect": "3.11.3", "it-stream-types": "2.0.2", "libp2p": "2.3.1", "multiformats": "13.3.1", "uint8arraylist": "2.4.8" }, "devDependencies": { - "@effect/platform": "0.70.3", - "@effect/platform-node": "0.65.3", + "@effect/platform": "0.70.4", + "@effect/platform-node": "0.65.4", "@effect/vitest": "0.14.2", "@local/eslint-config": "0.0.0-private", "@local/tsconfig": "0.0.0-private", diff --git a/yarn.lock b/yarn.lock index 58e1de44adb..76a48cd83db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5330,43 +5330,43 @@ __metadata: languageName: node linkType: hard -"@effect/platform-node-shared@npm:^0.20.3": - version: 0.20.3 - resolution: "@effect/platform-node-shared@npm:0.20.3" +"@effect/platform-node-shared@npm:^0.20.4": + version: 0.20.4 + resolution: "@effect/platform-node-shared@npm:0.20.4" dependencies: "@parcel/watcher": "npm:^2.4.1" multipasta: "npm:^0.2.5" peerDependencies: - "@effect/platform": ^0.70.3 - effect: ^3.11.2 - checksum: 10c0/f3a916b12f01d13d2ce7f961b04d41f9081fb25c0495627696be895bf08b797160ab8207fb9b2da492a7ece09985a203f51fc9732f825494dad531bc36689921 + "@effect/platform": ^0.70.4 + effect: ^3.11.3 + checksum: 10c0/dca46400c108c1e6ee09010973ebd5ee21b83c660e93c6c5ed36eff1fd157c36b415690f58c3befe83dd5675d202336670c75d04a5fbea8b7a46c6edb38343d1 languageName: node linkType: hard -"@effect/platform-node@npm:0.65.3": - version: 0.65.3 - resolution: "@effect/platform-node@npm:0.65.3" +"@effect/platform-node@npm:0.65.4": + version: 0.65.4 + resolution: "@effect/platform-node@npm:0.65.4" dependencies: - "@effect/platform-node-shared": "npm:^0.20.3" + "@effect/platform-node-shared": "npm:^0.20.4" mime: "npm:^3.0.0" undici: "npm:^6.19.7" ws: "npm:^8.18.0" peerDependencies: - "@effect/platform": ^0.70.3 - effect: ^3.11.2 - checksum: 10c0/a6d5e24c718a9ea7b174d698a969332767bfeecea2dc74a565dd0d6a4700c315a50f633275b6cecc83504909ff7143a8e3f4a8d4453382f9f98344b1f5a1f698 + "@effect/platform": ^0.70.4 + effect: ^3.11.3 + checksum: 10c0/99915676a36f64e90ef2b2533ab0789f11ba8bc8cc19cb7cfda99e979f6d470f36ddb670eb905491b957fda1f2bde97c463a0c84f634aa12c864ebdf5f6fd1d1 languageName: node linkType: hard -"@effect/platform@npm:0.70.3": - version: 0.70.3 - resolution: "@effect/platform@npm:0.70.3" +"@effect/platform@npm:0.70.4": + version: 0.70.4 + resolution: "@effect/platform@npm:0.70.4" dependencies: find-my-way-ts: "npm:^0.1.5" multipasta: "npm:^0.2.5" peerDependencies: - effect: ^3.11.2 - checksum: 10c0/d6afb4d60ebce291b845873ae7323437844f0b2e9dc622dd429385306bf0aade78c10b29f5201c21475e572eeac8035bbfe93cc38beafe65b81d4f02dfc97777 + effect: ^3.11.3 + checksum: 10c0/d55e37943b3fcaca7e15e74b7c8dbd562d2cd028f684b9fbc9db0e82d9048c2b99954450516c2260ac5e6eba9529f1b5884c7bd0d8e180d1485405178f49c71e languageName: node linkType: hard @@ -8446,8 +8446,8 @@ __metadata: dependencies: "@chainsafe/libp2p-noise": "npm:16.0.0" "@chainsafe/libp2p-yamux": "npm:7.0.1" - "@effect/platform": "npm:0.70.3" - "@effect/platform-node": "npm:0.65.3" + "@effect/platform": "npm:0.70.4" + "@effect/platform-node": "npm:0.65.4" "@effect/vitest": "npm:0.14.2" "@libp2p/crypto": "npm:5.0.7" "@libp2p/identify": "npm:3.0.12" @@ -8460,7 +8460,7 @@ __metadata: "@rust/harpc-wire-protocol": "npm:0.0.0-private" "@types/node": "npm:22.10.1" "@vitest/coverage-istanbul": "npm:2.1.8" - effect: "npm:3.11.2" + effect: "npm:3.11.3" eslint: "npm:8.57.0" it-stream-types: "npm:2.0.2" libp2p: "npm:2.3.1" @@ -24430,12 +24430,12 @@ __metadata: languageName: node linkType: hard -"effect@npm:3.11.2": - version: 3.11.2 - resolution: "effect@npm:3.11.2" +"effect@npm:3.11.3": + version: 3.11.3 + resolution: "effect@npm:3.11.3" dependencies: fast-check: "npm:^3.21.0" - checksum: 10c0/04a04326c59d4557050744617eb2c6c5e1a1694990e7dddfda56c6023292197691d21c5b7598ba79a726a12adef1745246e262511991edcc62fb7300097b2989 + checksum: 10c0/a343a20d833112e21b27f17f404e97a33240fafe09d638428381d8bb96b4f0d2eccdaebe43453dce4982242134e1c8cfbab1d63e364fea79de02faeb92940c04 languageName: node linkType: hard From 31c99987d2084fbe0158c38fddc0b66e94684447 Mon Sep 17 00:00:00 2001 From: "hash-worker[bot]" <180894564+hash-worker[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 00:53:26 +0000 Subject: [PATCH 03/24] Update npm package `@dnd-kit/core` to v6.3.1 (#5816) Co-authored-by: hash-worker[bot] <180894564+hash-worker[bot]@users.noreply.github.com> --- apps/hash-frontend/package.json | 2 +- blocks/kanban-board/package.json | 2 +- blocks/shuffle/package.json | 2 +- yarn.lock | 14 +++++++------- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/hash-frontend/package.json b/apps/hash-frontend/package.json index f082c9e8e2c..41bcddcecae 100644 --- a/apps/hash-frontend/package.json +++ b/apps/hash-frontend/package.json @@ -24,7 +24,7 @@ "@blockprotocol/hook": "0.1.3", "@blockprotocol/service": "0.1.4", "@blockprotocol/type-system": "0.1.2-canary.0", - "@dnd-kit/core": "6.3.0", + "@dnd-kit/core": "6.3.1", "@dnd-kit/sortable": "7.0.2", "@dnd-kit/utilities": "3.2.2", "@emoji-mart/react": "1.0.1", diff --git a/blocks/kanban-board/package.json b/blocks/kanban-board/package.json index 08503ee66c4..43fc1293725 100644 --- a/blocks/kanban-board/package.json +++ b/blocks/kanban-board/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@blockprotocol/graph": "0.3.4", - "@dnd-kit/core": "6.3.0", + "@dnd-kit/core": "6.3.1", "@dnd-kit/sortable": "7.0.2", "@dnd-kit/utilities": "3.2.2", "@hashintel/block-design-system": "0.0.2", diff --git a/blocks/shuffle/package.json b/blocks/shuffle/package.json index ca1cb654170..c9693b5dc42 100644 --- a/blocks/shuffle/package.json +++ b/blocks/shuffle/package.json @@ -28,7 +28,7 @@ }, "dependencies": { "@blockprotocol/graph": "0.3.4", - "@dnd-kit/core": "6.3.0", + "@dnd-kit/core": "6.3.1", "@dnd-kit/sortable": "7.0.2", "@dnd-kit/utilities": "3.2.2", "@hashintel/design-system": "0.0.8", diff --git a/yarn.lock b/yarn.lock index 76a48cd83db..03529ce8f30 100644 --- a/yarn.lock +++ b/yarn.lock @@ -487,7 +487,7 @@ __metadata: "@blockprotocol/hook": "npm:0.1.3" "@blockprotocol/service": "npm:0.1.4" "@blockprotocol/type-system": "npm:0.1.2-canary.0" - "@dnd-kit/core": "npm:6.3.0" + "@dnd-kit/core": "npm:6.3.1" "@dnd-kit/sortable": "npm:7.0.2" "@dnd-kit/utilities": "npm:3.2.2" "@emoji-mart/react": "npm:1.0.1" @@ -4577,7 +4577,7 @@ __metadata: resolution: "@blocks/kanban-board@workspace:blocks/kanban-board" dependencies: "@blockprotocol/graph": "npm:0.3.4" - "@dnd-kit/core": "npm:6.3.0" + "@dnd-kit/core": "npm:6.3.1" "@dnd-kit/sortable": "npm:7.0.2" "@dnd-kit/utilities": "npm:3.2.2" "@hashintel/block-design-system": "npm:0.0.2" @@ -4675,7 +4675,7 @@ __metadata: resolution: "@blocks/shuffle@workspace:blocks/shuffle" dependencies: "@blockprotocol/graph": "npm:0.3.4" - "@dnd-kit/core": "npm:6.3.0" + "@dnd-kit/core": "npm:6.3.1" "@dnd-kit/sortable": "npm:7.0.2" "@dnd-kit/utilities": "npm:3.2.2" "@hashintel/design-system": "npm:0.0.8" @@ -5292,9 +5292,9 @@ __metadata: languageName: node linkType: hard -"@dnd-kit/core@npm:6.3.0": - version: 6.3.0 - resolution: "@dnd-kit/core@npm:6.3.0" +"@dnd-kit/core@npm:6.3.1": + version: 6.3.1 + resolution: "@dnd-kit/core@npm:6.3.1" dependencies: "@dnd-kit/accessibility": "npm:^3.1.1" "@dnd-kit/utilities": "npm:^3.2.2" @@ -5302,7 +5302,7 @@ __metadata: peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: 10c0/7b6ebae619921ac27a367b5f168cf66729ab2f5041a5376bb05399cdfd05e03c8fac5d218632ee04fe7c1e049bde9222930c6e8ee1dbb0ca8bde38038f1a1a48 + checksum: 10c0/196db95d81096d9dc248983533eab91ba83591770fa5c894b1ac776f42af0d99522b3fd5bb3923411470e4733fcfa103e6ee17adc17b9b7eb54c7fbec5ff7c52 languageName: node linkType: hard From 9e511fadcb5c7ae8b654608054b4a069328fe0cf Mon Sep 17 00:00:00 2001 From: "hash-worker[bot]" <180894564+hash-worker[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 01:52:28 +0000 Subject: [PATCH 04/24] Update npm package `@effect/vitest` to v0.14.3 (#5817) Co-authored-by: hash-worker[bot] <180894564+hash-worker[bot]@users.noreply.github.com> --- libs/@local/harpc/client/typescript/package.json | 2 +- yarn.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libs/@local/harpc/client/typescript/package.json b/libs/@local/harpc/client/typescript/package.json index ebc08aa75d9..8eb5e18a9b4 100644 --- a/libs/@local/harpc/client/typescript/package.json +++ b/libs/@local/harpc/client/typescript/package.json @@ -36,7 +36,7 @@ "devDependencies": { "@effect/platform": "0.70.4", "@effect/platform-node": "0.65.4", - "@effect/vitest": "0.14.2", + "@effect/vitest": "0.14.3", "@local/eslint-config": "0.0.0-private", "@local/tsconfig": "0.0.0-private", "@rust/harpc-wire-protocol": "0.0.0-private", diff --git a/yarn.lock b/yarn.lock index 03529ce8f30..38dffe87c6e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5370,13 +5370,13 @@ __metadata: languageName: node linkType: hard -"@effect/vitest@npm:0.14.2": - version: 0.14.2 - resolution: "@effect/vitest@npm:0.14.2" +"@effect/vitest@npm:0.14.3": + version: 0.14.3 + resolution: "@effect/vitest@npm:0.14.3" peerDependencies: - effect: ^3.11.2 + effect: ^3.11.3 vitest: ^2.0.5 - checksum: 10c0/7a313a0bdafa34169ab5547389d14ee2fad7a6bdcc2b5c6a0a2e26cf215851a1002532e339be90e6bbbd8ae45de6d39e4907b704c2c3c1e7a3816d109213027b + checksum: 10c0/06c1630e03b67eab565913090918532cdace2655cf6345accb42db5f60a01cfe1fbd03d6119d9c03554786ad2584520cc06034ebab7d6432355920133a8df53c languageName: node linkType: hard @@ -8448,7 +8448,7 @@ __metadata: "@chainsafe/libp2p-yamux": "npm:7.0.1" "@effect/platform": "npm:0.70.4" "@effect/platform-node": "npm:0.65.4" - "@effect/vitest": "npm:0.14.2" + "@effect/vitest": "npm:0.14.3" "@libp2p/crypto": "npm:5.0.7" "@libp2p/identify": "npm:3.0.12" "@libp2p/interface": "npm:2.2.1" From fa5de6488c0760cae9249792a3ca8b949152f277 Mon Sep 17 00:00:00 2001 From: "hash-worker[bot]" <180894564+hash-worker[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 08:28:31 +0000 Subject: [PATCH 05/24] Update npm package `express` to v4.21.2 (#5818) Co-authored-by: hash-worker[bot] <180894564+hash-worker[bot]@users.noreply.github.com> Co-authored-by: vilkinsons <6226576+vilkinsons@users.noreply.github.com> --- apps/hash-api/package.json | 2 +- yarn.lock | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/hash-api/package.json b/apps/hash-api/package.json index de760202aad..1ab6e5aff72 100644 --- a/apps/hash-api/package.json +++ b/apps/hash-api/package.json @@ -73,7 +73,7 @@ "cross-env": "7.0.3", "dedent": "0.7.0", "exponential-backoff": "3.1.1", - "express": "4.21.1", + "express": "4.21.2", "express-handlebars": "7.1.3", "express-http-proxy": "2.1.1", "express-rate-limit": "7.4.1", diff --git a/yarn.lock b/yarn.lock index 38dffe87c6e..9b76b2c2678 100644 --- a/yarn.lock +++ b/yarn.lock @@ -435,7 +435,7 @@ __metadata: dedent: "npm:0.7.0" eslint: "npm:8.57.0" exponential-backoff: "npm:3.1.1" - express: "npm:4.21.1" + express: "npm:4.21.2" express-handlebars: "npm:7.1.3" express-http-proxy: "npm:2.1.1" express-rate-limit: "npm:7.4.1" @@ -25976,9 +25976,9 @@ __metadata: languageName: node linkType: hard -"express@npm:4.21.1, express@npm:^4.17.3": - version: 4.21.1 - resolution: "express@npm:4.21.1" +"express@npm:4.21.2, express@npm:^4.17.3": + version: 4.21.2 + resolution: "express@npm:4.21.2" dependencies: accepts: "npm:~1.3.8" array-flatten: "npm:1.1.1" @@ -25999,7 +25999,7 @@ __metadata: methods: "npm:~1.1.2" on-finished: "npm:2.4.1" parseurl: "npm:~1.3.3" - path-to-regexp: "npm:0.1.10" + path-to-regexp: "npm:0.1.12" proxy-addr: "npm:~2.0.7" qs: "npm:6.13.0" range-parser: "npm:~1.2.1" @@ -26011,7 +26011,7 @@ __metadata: type-is: "npm:~1.6.18" utils-merge: "npm:1.0.1" vary: "npm:~1.1.2" - checksum: 10c0/0c287867e5f6129d3def1edd9b63103a53c40d4dc8628839d4b6827e35eb8f0de5a4656f9d85f4457eba584f9871ebb2ad26c750b36bd75d9bbb8bcebdc4892c + checksum: 10c0/38168fd0a32756600b56e6214afecf4fc79ec28eca7f7a91c2ab8d50df4f47562ca3f9dee412da7f5cea6b1a1544b33b40f9f8586dbacfbdada0fe90dbb10a1f languageName: node linkType: hard @@ -37061,10 +37061,10 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:0.1.10": - version: 0.1.10 - resolution: "path-to-regexp@npm:0.1.10" - checksum: 10c0/34196775b9113ca6df88e94c8d83ba82c0e1a2063dd33bfe2803a980da8d49b91db8104f49d5191b44ea780d46b8670ce2b7f4a5e349b0c48c6779b653f1afe4 +"path-to-regexp@npm:0.1.12": + version: 0.1.12 + resolution: "path-to-regexp@npm:0.1.12" + checksum: 10c0/1c6ff10ca169b773f3bba943bbc6a07182e332464704572962d277b900aeee81ac6aa5d060ff9e01149636c30b1f63af6e69dd7786ba6e0ddb39d4dee1f0645b languageName: node linkType: hard From 30313bebe22ff1f4e7f5bfe04e8d8bbc1a5e0530 Mon Sep 17 00:00:00 2001 From: "hash-worker[bot]" <180894564+hash-worker[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 08:33:28 +0000 Subject: [PATCH 06/24] Update npm package `webpack` to v5.97.1 (#5819) Co-authored-by: hash-worker[bot] <180894564+hash-worker[bot]@users.noreply.github.com> Co-authored-by: vilkinsons <6226576+vilkinsons@users.noreply.github.com> --- apps/hash-frontend/package.json | 2 +- apps/plugin-browser/package.json | 2 +- yarn.lock | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/hash-frontend/package.json b/apps/hash-frontend/package.json index 41bcddcecae..c37c979ffa6 100644 --- a/apps/hash-frontend/package.json +++ b/apps/hash-frontend/package.json @@ -156,7 +156,7 @@ "sass": "1.82.0", "typescript": "5.6.3", "wait-on": "8.0.1", - "webpack": "5.97.0" + "webpack": "5.97.1" }, "engines": { "node": ">= v20" diff --git a/apps/plugin-browser/package.json b/apps/plugin-browser/package.json index 81960c44484..95c3205b499 100755 --- a/apps/plugin-browser/package.json +++ b/apps/plugin-browser/package.json @@ -92,7 +92,7 @@ "tsconfig-paths-webpack-plugin": "4.2.0", "type-fest": "3.13.1", "typescript": "5.6.3", - "webpack": "5.97.0", + "webpack": "5.97.1", "webpack-cli": "4.10.0", "webpack-dev-server": "4.15.2", "zip-webpack-plugin": "4.0.2" diff --git a/yarn.lock b/yarn.lock index 9b76b2c2678..28e6ce0dc93 100644 --- a/yarn.lock +++ b/yarn.lock @@ -617,7 +617,7 @@ __metadata: use-font-face-observer: "npm:1.2.2" uuid: "npm:9.0.1" wait-on: "npm:8.0.1" - webpack: "npm:5.97.0" + webpack: "npm:5.97.1" languageName: unknown linkType: soft @@ -872,7 +872,7 @@ __metadata: typescript: "npm:5.6.3" uuid: "npm:9.0.1" webextension-polyfill: "npm:0.10.0" - webpack: "npm:5.97.0" + webpack: "npm:5.97.1" webpack-cli: "npm:4.10.0" webpack-dev-server: "npm:4.15.2" ws: "npm:8.18.0" @@ -46190,9 +46190,9 @@ __metadata: languageName: node linkType: hard -"webpack@npm:5, webpack@npm:5.97.0, webpack@npm:^5.72.0, webpack@npm:^5.76.0, webpack@npm:^5.94.0": - version: 5.97.0 - resolution: "webpack@npm:5.97.0" +"webpack@npm:5, webpack@npm:5.97.1, webpack@npm:^5.72.0, webpack@npm:^5.76.0, webpack@npm:^5.94.0": + version: 5.97.1 + resolution: "webpack@npm:5.97.1" dependencies: "@types/eslint-scope": "npm:^3.7.7" "@types/estree": "npm:^1.0.6" @@ -46222,7 +46222,7 @@ __metadata: optional: true bin: webpack: bin/webpack.js - checksum: 10c0/a8714d42defbf52382b61c157f68e161a16d0edf228d8d9abaa7a165f3ee0ac7386a08d28d4dcf8d6740ea5bda0c4d4abfeeb838df029e636c1c28bb2454ac56 + checksum: 10c0/a12d3dc882ca582075f2c4bd88840be8307427245c90a8a0e0b372d73560df13fcf25a61625c9e7edc964981d16b5a8323640562eb48347cf9dd2f8bd1b39d35 languageName: node linkType: hard From 2da84d8b9da86c19e7d442b6b6e9f91ac2430cf1 Mon Sep 17 00:00:00 2001 From: "hash-worker[bot]" <180894564+hash-worker[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 08:58:14 +0000 Subject: [PATCH 07/24] Update npm package `@google-cloud/vertexai` to v1.9.2 (#5821) Co-authored-by: hash-worker[bot] <180894564+hash-worker[bot]@users.noreply.github.com> --- apps/hash-ai-worker-ts/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/hash-ai-worker-ts/package.json b/apps/hash-ai-worker-ts/package.json index 44a12127610..affa008a7a9 100644 --- a/apps/hash-ai-worker-ts/package.json +++ b/apps/hash-ai-worker-ts/package.json @@ -47,7 +47,7 @@ "@blockprotocol/graph": "0.4.0-canary.0", "@blockprotocol/type-system": "0.1.2-canary.0", "@google-cloud/storage": "7.14.0", - "@google-cloud/vertexai": "1.9.0", + "@google-cloud/vertexai": "1.9.2", "@local/advanced-types": "0.0.0-private", "@local/hash-backend-utils": "0.0.0-private", "@local/hash-graph-client": "0.0.0-private", diff --git a/yarn.lock b/yarn.lock index 28e6ce0dc93..4fdc68e3be4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -293,7 +293,7 @@ __metadata: "@blockprotocol/graph": "npm:0.4.0-canary.0" "@blockprotocol/type-system": "npm:0.1.2-canary.0" "@google-cloud/storage": "npm:7.14.0" - "@google-cloud/vertexai": "npm:1.9.0" + "@google-cloud/vertexai": "npm:1.9.2" "@local/advanced-types": "npm:0.0.0-private" "@local/eslint-config": "npm:0.0.0-private" "@local/hash-backend-utils": "npm:0.0.0-private" @@ -6304,12 +6304,12 @@ __metadata: languageName: node linkType: hard -"@google-cloud/vertexai@npm:1.9.0": - version: 1.9.0 - resolution: "@google-cloud/vertexai@npm:1.9.0" +"@google-cloud/vertexai@npm:1.9.2": + version: 1.9.2 + resolution: "@google-cloud/vertexai@npm:1.9.2" dependencies: google-auth-library: "npm:^9.1.0" - checksum: 10c0/6568fa105a180b4d27be31be92e6c71b9f8d018bb432631d63e0bb37cc299811895dbf237b9c560a1558de4f87b18882bdbb8e976ce43bf75bf80d998f395d76 + checksum: 10c0/d380fb9a1857019acdec6da99ab307bc3a9fe2d096138bf7ff7dc4dcc3c69ab8722a512a4ea365ac6c3311b808e230ecbddfd4ec949d98a69d22878a6bb002c5 languageName: node linkType: hard From 080868eddbf34683a4372943ffcd7c15159f0342 Mon Sep 17 00:00:00 2001 From: "hash-worker[bot]" <180894564+hash-worker[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 09:09:51 +0000 Subject: [PATCH 08/24] Update npm package `lefthook` to v1.9.0 (#5822) Co-authored-by: hash-worker[bot] <180894564+hash-worker[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 92 ++++++++++++++++++++++++++-------------------------- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/package.json b/package.json index 8de76f26dc4..2998d109981 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,7 @@ "@taplo/cli": "0.7.0", "@yarnpkg/types": "^4.0.0", "concurrently": "7.6.0", - "lefthook": "1.8.5", + "lefthook": "1.9.0", "lockfile-lint": "4.14.0", "markdownlint-cli": "0.43.0", "npm-run-all2": "7.0.1", diff --git a/yarn.lock b/yarn.lock index 4fdc68e3be4..d8ba96c763e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -28265,7 +28265,7 @@ __metadata: "@taplo/cli": "npm:0.7.0" "@yarnpkg/types": "npm:^4.0.0" concurrently: "npm:7.6.0" - lefthook: "npm:1.8.5" + lefthook: "npm:1.9.0" lockfile-lint: "npm:4.14.0" markdownlint-cli: "npm:0.43.0" npm-run-all2: "npm:7.0.1" @@ -31596,90 +31596,90 @@ __metadata: languageName: node linkType: hard -"lefthook-darwin-arm64@npm:1.8.5": - version: 1.8.5 - resolution: "lefthook-darwin-arm64@npm:1.8.5" +"lefthook-darwin-arm64@npm:1.9.0": + version: 1.9.0 + resolution: "lefthook-darwin-arm64@npm:1.9.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"lefthook-darwin-x64@npm:1.8.5": - version: 1.8.5 - resolution: "lefthook-darwin-x64@npm:1.8.5" +"lefthook-darwin-x64@npm:1.9.0": + version: 1.9.0 + resolution: "lefthook-darwin-x64@npm:1.9.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"lefthook-freebsd-arm64@npm:1.8.5": - version: 1.8.5 - resolution: "lefthook-freebsd-arm64@npm:1.8.5" +"lefthook-freebsd-arm64@npm:1.9.0": + version: 1.9.0 + resolution: "lefthook-freebsd-arm64@npm:1.9.0" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"lefthook-freebsd-x64@npm:1.8.5": - version: 1.8.5 - resolution: "lefthook-freebsd-x64@npm:1.8.5" +"lefthook-freebsd-x64@npm:1.9.0": + version: 1.9.0 + resolution: "lefthook-freebsd-x64@npm:1.9.0" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"lefthook-linux-arm64@npm:1.8.5": - version: 1.8.5 - resolution: "lefthook-linux-arm64@npm:1.8.5" +"lefthook-linux-arm64@npm:1.9.0": + version: 1.9.0 + resolution: "lefthook-linux-arm64@npm:1.9.0" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"lefthook-linux-x64@npm:1.8.5": - version: 1.8.5 - resolution: "lefthook-linux-x64@npm:1.8.5" +"lefthook-linux-x64@npm:1.9.0": + version: 1.9.0 + resolution: "lefthook-linux-x64@npm:1.9.0" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"lefthook-openbsd-arm64@npm:1.8.5": - version: 1.8.5 - resolution: "lefthook-openbsd-arm64@npm:1.8.5" +"lefthook-openbsd-arm64@npm:1.9.0": + version: 1.9.0 + resolution: "lefthook-openbsd-arm64@npm:1.9.0" conditions: os=openbsd & cpu=arm64 languageName: node linkType: hard -"lefthook-openbsd-x64@npm:1.8.5": - version: 1.8.5 - resolution: "lefthook-openbsd-x64@npm:1.8.5" +"lefthook-openbsd-x64@npm:1.9.0": + version: 1.9.0 + resolution: "lefthook-openbsd-x64@npm:1.9.0" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"lefthook-windows-arm64@npm:1.8.5": - version: 1.8.5 - resolution: "lefthook-windows-arm64@npm:1.8.5" +"lefthook-windows-arm64@npm:1.9.0": + version: 1.9.0 + resolution: "lefthook-windows-arm64@npm:1.9.0" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"lefthook-windows-x64@npm:1.8.5": - version: 1.8.5 - resolution: "lefthook-windows-x64@npm:1.8.5" +"lefthook-windows-x64@npm:1.9.0": + version: 1.9.0 + resolution: "lefthook-windows-x64@npm:1.9.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"lefthook@npm:1.8.5": - version: 1.8.5 - resolution: "lefthook@npm:1.8.5" - dependencies: - lefthook-darwin-arm64: "npm:1.8.5" - lefthook-darwin-x64: "npm:1.8.5" - lefthook-freebsd-arm64: "npm:1.8.5" - lefthook-freebsd-x64: "npm:1.8.5" - lefthook-linux-arm64: "npm:1.8.5" - lefthook-linux-x64: "npm:1.8.5" - lefthook-openbsd-arm64: "npm:1.8.5" - lefthook-openbsd-x64: "npm:1.8.5" - lefthook-windows-arm64: "npm:1.8.5" - lefthook-windows-x64: "npm:1.8.5" +"lefthook@npm:1.9.0": + version: 1.9.0 + resolution: "lefthook@npm:1.9.0" + dependencies: + lefthook-darwin-arm64: "npm:1.9.0" + lefthook-darwin-x64: "npm:1.9.0" + lefthook-freebsd-arm64: "npm:1.9.0" + lefthook-freebsd-x64: "npm:1.9.0" + lefthook-linux-arm64: "npm:1.9.0" + lefthook-linux-x64: "npm:1.9.0" + lefthook-openbsd-arm64: "npm:1.9.0" + lefthook-openbsd-x64: "npm:1.9.0" + lefthook-windows-arm64: "npm:1.9.0" + lefthook-windows-x64: "npm:1.9.0" dependenciesMeta: lefthook-darwin-arm64: optional: true @@ -31703,7 +31703,7 @@ __metadata: optional: true bin: lefthook: bin/index.js - checksum: 10c0/71f16a752c38f732b9bf1163dd552c9e92231df173c4ebb908f7ab634c5c01a813f6b3b325f1e749bc92c07c0cacf44613bb1251f97cd49d160c360344c3aa48 + checksum: 10c0/f013cca0257758e023abc68525793c18ab29353c222670e16a907274346483c8ca3d3a9f8d2f7d51dc9154e356da9f069c2423500eb055991930ebdc2c6f26c6 languageName: node linkType: hard From f7decaa4f50da6fa1674185a277ec43d342e9c27 Mon Sep 17 00:00:00 2001 From: "hash-worker[bot]" <180894564+hash-worker[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 09:57:07 +0000 Subject: [PATCH 09/24] Update GitHub Action `actions/cache` to v4.2.0 (#5823) Co-authored-by: hash-worker[bot] <180894564+hash-worker[bot]@users.noreply.github.com> --- .github/actions/docker-build-push/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/docker-build-push/action.yml b/.github/actions/docker-build-push/action.yml index 0eaa0c1d09e..679db526b93 100644 --- a/.github/actions/docker-build-push/action.yml +++ b/.github/actions/docker-build-push/action.yml @@ -50,7 +50,7 @@ runs: AWS_REGION: ${{ inputs.AWS_REGION }} - name: Cache Docker layers - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: /tmp/.buildx-cache key: docker-${{inputs.SHORTNAME}}-${{ hashFiles('yarn.lock') }} From 9768eb8937d3980510fb0d98e75a52a9f516d71f Mon Sep 17 00:00:00 2001 From: Ciaran Morinan <37743469+CiaranMn@users.noreply.github.com> Date: Fri, 6 Dec 2024 09:59:18 +0000 Subject: [PATCH 10/24] H-3699, H-3374: Use and set value `dataTypeId` in the entity editor (#5813) --- ...-doc-company-and-person-types.migration.ts | 3 + .../src/graph/knowledge/primitive/entity.ts | 21 ++ apps/hash-api/src/graphql/resolvers/index.ts | 2 + .../resolvers/knowledge/entity/entity.ts | 16 ++ .../queries/knowledge/entity.queries.ts | 14 + .../entities/[entity-uuid].page.tsx | 3 +- .../[entity-uuid].page/create-entity-page.tsx | 53 +++- .../edit-entity-slide-over.tsx | 1 + .../entity-editor/claims-section.tsx | 16 +- .../entity-editor/entity-editor-context.tsx | 20 +- .../entity-editor/file-preview-section.tsx | 13 +- .../history-section/get-history-events.ts | 33 ++- .../entity-editor/link-section.tsx | 12 +- .../entity-editor/links-section.tsx | 5 +- .../links-section/outgoing-links-section.tsx | 9 +- .../linked-entity-list-editor.tsx | 5 +- .../linked-entity-selector.tsx | 20 +- .../linked-with-cell-editor.tsx | 6 +- .../outgoing-links-section/use-rows.ts | 5 +- .../property-table/cells/change-type-cell.tsx | 16 +- .../property-table/cells/value-cell.tsx | 160 ++++++------ .../cells/value-cell/array-editor.tsx | 138 ++++++++-- .../value-cell/array-editor/draft-row.tsx | 29 +-- .../value-cell/array-editor/sortable-row.tsx | 102 +++----- .../cells/value-cell/array-editor/types.ts | 5 +- .../value-cell/array-editor/value-chip.tsx | 3 +- .../cells/value-cell/editor-specs.ts | 38 +-- .../cells/value-cell/editor-type-picker.tsx | 19 +- .../inputs/number-or-text-input.tsx | 47 ++-- .../cells/value-cell/single-value-editor.tsx | 226 ++++++++++------- .../property-table/cells/value-cell/types.ts | 12 +- .../property-table/cells/value-cell/utils.ts | 95 ------- .../property-table/types.ts | 29 ++- .../use-create-get-cell-content.ts | 72 +++--- .../use-create-on-cell-edited.ts | 20 +- .../generate-property-row-recursively.ts | 30 ++- .../use-rows/use-property-rows-from-entity.ts | 72 +++++- .../entity-editor/types-section.tsx | 4 +- .../use-get-type-change-details.ts | 15 +- .../shared/create-draft-entity-subgraph.ts | 12 +- .../src/pages/shared/data-types-context.tsx | 31 +-- .../src/pages/shared/entity-selector.tsx | 37 ++- .../src/pages/shared/use-validate-entity.ts | 124 +++++++++ .../src/shared/file-upload-context.tsx | 18 +- .../selector-autocomplete-option.tsx | 15 +- .../src/shared/data-types-options-context.tsx | 7 +- .../@local/graph/sdk/typescript/src/entity.ts | 227 ++++++++++++++++- .../graph/sdk/typescript/tests/entity.test.ts | 84 ++++-- .../graph/types/typescript/src/entity.ts | 1 + .../hash-isomorphic-utils/src/data-types.ts | 239 +++++++++++++++--- .../src/generate-entity-label.ts | 4 +- .../src/graphql/scalar-mapping.ts | 2 + .../type-defs/knowledge/entity.typedef.ts | 27 ++ 53 files changed, 1494 insertions(+), 723 deletions(-) create mode 100644 apps/hash-frontend/src/pages/shared/use-validate-entity.ts diff --git a/apps/hash-api/src/graph/ensure-system-graph-is-initialized/migrate-ontology-types/migrations/019-add-doc-company-and-person-types.migration.ts b/apps/hash-api/src/graph/ensure-system-graph-is-initialized/migrate-ontology-types/migrations/019-add-doc-company-and-person-types.migration.ts index c312eebab0a..183d6340aef 100644 --- a/apps/hash-api/src/graph/ensure-system-graph-is-initialized/migrate-ontology-types/migrations/019-add-doc-company-and-person-types.migration.ts +++ b/apps/hash-api/src/graph/ensure-system-graph-is-initialized/migrate-ontology-types/migrations/019-add-doc-company-and-person-types.migration.ts @@ -378,6 +378,9 @@ const migrate: MigrationFunction = async ({ { entityTypeDefinition: { title: "Person", + /** + * @todo when updating this, add plural title + */ icon: "👤", /** @todo improve this desc */ description: "A human being", diff --git a/apps/hash-api/src/graph/knowledge/primitive/entity.ts b/apps/hash-api/src/graph/knowledge/primitive/entity.ts index 7ee8f66d90e..7942a671bf8 100644 --- a/apps/hash-api/src/graph/knowledge/primitive/entity.ts +++ b/apps/hash-api/src/graph/knowledge/primitive/entity.ts @@ -13,6 +13,7 @@ import type { GraphResolveDepths, ModifyRelationshipOperation, } from "@local/hash-graph-client"; +import type { ValidateEntityParamsComponents } from "@local/hash-graph-client/api"; import type { CreateEntityParameters } from "@local/hash-graph-sdk/entity"; import { Entity, LinkEntity } from "@local/hash-graph-sdk/entity"; import type { @@ -24,6 +25,7 @@ import type { EntityProperties, LinkData, PropertyObject, + PropertyObjectWithMetadata, PropertyPatchOperation, } from "@local/hash-graph-types/entity"; import type { BaseUrl } from "@local/hash-graph-types/ontology"; @@ -593,6 +595,25 @@ export const updateEntity = async ( return updatedEntity; }; +export const validateEntity: ImpureGraphFunction< + { + components: ValidateEntityParamsComponents; + entityTypes: VersionedUrl[]; + properties: PropertyObjectWithMetadata; + }, + Promise +> = async (context, authentication, params) => { + const { components, entityTypes, properties } = params; + + return await context.graphApi + .validateEntity(authentication.actorId, { + components, + entityTypes, + properties, + }) + .then(({ data }) => data); +}; + /** * Get the incoming links of an entity. * diff --git a/apps/hash-api/src/graphql/resolvers/index.ts b/apps/hash-api/src/graphql/resolvers/index.ts index 18fe1851f53..9accefc8488 100644 --- a/apps/hash-api/src/graphql/resolvers/index.ts +++ b/apps/hash-api/src/graphql/resolvers/index.ts @@ -53,6 +53,7 @@ import { removeEntityViewerResolver, updateEntitiesResolver, updateEntityResolver, + validateEntityResolver, } from "./knowledge/entity/entity"; import { getEntityDiffsResolver } from "./knowledge/entity/get-entity-diffs"; import { createFileFromUrl } from "./knowledge/file/create-file-from-url"; @@ -147,6 +148,7 @@ export const resolvers: Omit & { generateInverse: loggedInMiddleware(generateInverseResolver), generatePlural: loggedInMiddleware(generatePluralResolver), isGenerationAvailable: isGenerationAvailableResolver, + validateEntity: validateEntityResolver, }, Mutation: { diff --git a/apps/hash-api/src/graphql/resolvers/knowledge/entity/entity.ts b/apps/hash-api/src/graphql/resolvers/knowledge/entity/entity.ts index 4a72d9cdfec..d63a845723f 100644 --- a/apps/hash-api/src/graphql/resolvers/knowledge/entity/entity.ts +++ b/apps/hash-api/src/graphql/resolvers/knowledge/entity/entity.ts @@ -39,6 +39,7 @@ import { removeEntityAdministrator, removeEntityEditor, updateEntity, + validateEntity, } from "../../../../graph/knowledge/primitive/entity"; import { createLinkEntity, @@ -63,6 +64,7 @@ import type { QueryGetEntitySubgraphArgs, QueryIsEntityPublicArgs, QueryResolvers, + QueryValidateEntityArgs, ResolverFn, } from "../../../api-types.gen"; import { @@ -401,6 +403,20 @@ export const updateEntitiesResolver: ResolverFn< return updatedEntities; }; +export const validateEntityResolver: ResolverFn< + Promise, + Record, + LoggedInGraphQLContext, + QueryValidateEntityArgs +> = async (_, params, graphQLContext) => { + const { authentication } = graphQLContext; + const context = graphQLContextToImpureGraphContext(graphQLContext); + + await validateEntity(context, authentication, params); + + return true; +}; + export const archiveEntityResolver: ResolverFn< Promise, Record, diff --git a/apps/hash-frontend/src/graphql/queries/knowledge/entity.queries.ts b/apps/hash-frontend/src/graphql/queries/knowledge/entity.queries.ts index 3fbba0c3a9d..f690f36727c 100644 --- a/apps/hash-frontend/src/graphql/queries/knowledge/entity.queries.ts +++ b/apps/hash-frontend/src/graphql/queries/knowledge/entity.queries.ts @@ -188,3 +188,17 @@ export const getEntityDiffsQuery = gql` } } `; + +export const validateEntityQuery = gql` + query validateEntity( + $components: ValidateEntityParamsComponents! + $entityTypes: [VersionedUrl!]! + $properties: PropertyObjectWithMetadata! + ) { + validateEntity( + components: $components + entityTypes: $entityTypes + properties: $properties + ) + } +`; diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page.tsx index 19e3ef3715e..8b66ccc5530 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page.tsx @@ -337,7 +337,7 @@ const Page: NextPageWithLayout = () => { oldProperties: entityFromDb?.properties ?? {}, newProperties: mergePropertyObjectAndMetadata( overrideProperties ?? draftEntity.properties, - undefined, + draftEntity.metadata.properties, ), }), }, @@ -457,6 +457,7 @@ const Page: NextPageWithLayout = () => { omitProperties: [], }), ); + setIsDirty(true); }} /> ); diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/create-entity-page.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/create-entity-page.tsx index a4d986d6245..1be0bb57c63 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/create-entity-page.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/create-entity-page.tsx @@ -1,5 +1,10 @@ +import { useMutation } from "@apollo/client"; import type { VersionedUrl } from "@blockprotocol/type-system"; import { AlertModal } from "@hashintel/design-system"; +import { + Entity, + mergePropertyObjectAndMetadata, +} from "@local/hash-graph-sdk/entity"; import type { PropertyObject } from "@local/hash-graph-types/entity"; import { generateEntityLabel } from "@local/hash-isomorphic-utils/generate-entity-label"; import { blockProtocolEntityTypes } from "@local/hash-isomorphic-utils/ontology-type-ids"; @@ -10,9 +15,17 @@ import { Typography } from "@mui/material"; import { useRouter } from "next/router"; import { useCallback, useContext, useEffect, useState } from "react"; -import { useBlockProtocolCreateEntity } from "../../../../components/hooks/block-protocol-functions/knowledge/use-block-protocol-create-entity"; import { PageErrorState } from "../../../../components/page-error-state"; +import type { + CreateEntityMutation, + CreateEntityMutationVariables, +} from "../../../../graphql/api-types.gen"; +import { + createEntityMutation, + getEntitySubgraphQuery, +} from "../../../../graphql/queries/knowledge/entity.queries"; import { Link } from "../../../../shared/ui/link"; +import { generateUseEntityTypeEntitiesQueryVariables } from "../../../../shared/use-entity-type-entities"; import { useGetClosedMultiEntityType } from "../../../shared/use-get-closed-multi-entity-type"; import { WorkspaceContext } from "../../../shared/workspace-context"; import { EditBar } from "../../shared/edit-bar"; @@ -79,9 +92,25 @@ export const CreateEntityPage = ({ entityTypeId }: CreateEntityPageProps) => { const { activeWorkspace, activeWorkspaceOwnedById } = useContext(WorkspaceContext); - const { createEntity } = useBlockProtocolCreateEntity( - activeWorkspaceOwnedById ?? null, - ); + + const [createEntity] = useMutation< + CreateEntityMutation, + CreateEntityMutationVariables + >(createEntityMutation, { + refetchQueries: [ + /** + * This refetch query accounts for the "Entities" section + * in the sidebar being updated when the first instance of + * a type is created by a user that is from a different web. + */ + { + query: getEntitySubgraphQuery, + variables: generateUseEntityTypeEntitiesQueryVariables({ + ownedById: activeWorkspaceOwnedById, + }), + }, + ], + }); const [creating, setCreating] = useState(false); @@ -110,13 +139,21 @@ export const CreateEntityPage = ({ entityTypeId }: CreateEntityPageProps) => { try { setCreating(true); - const { data: createdEntity } = await createEntity({ - data: { + const { data } = await createEntity({ + variables: { entityTypeIds: entity.metadata.entityTypeIds, - properties: overrideProperties ?? draftEntity.properties, + ownedById: activeWorkspaceOwnedById, + properties: mergePropertyObjectAndMetadata( + overrideProperties ?? draftEntity.properties, + draftEntity.metadata.properties, + ), }, }); + const createdEntity = data?.createEntity + ? new Entity(data.createEntity) + : null; + if (!createdEntity) { return; } @@ -139,7 +176,7 @@ export const CreateEntityPage = ({ entityTypeId }: CreateEntityPageProps) => { } }; - if (!draftEntityTypesDetails && !closedTypeLoading) { + if (closedTypeLoading) { return ; } diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/edit-entity-slide-over.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/edit-entity-slide-over.tsx index 897044abae6..aa228c730cd 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/edit-entity-slide-over.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/edit-entity-slide-over.tsx @@ -601,6 +601,7 @@ const EditEntitySlideOver = memo( omitProperties: [], }), ); + setIsDirty(true); }} isDirty={isDirty} onEntityClick={onEntityClick} diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/claims-section.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/claims-section.tsx index 0c33980383a..51410bab6ed 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/claims-section.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/claims-section.tsx @@ -12,7 +12,7 @@ import { type EntityRootType, extractEntityUuidFromEntityId, } from "@local/hash-subgraph"; -import { getRoots } from "@local/hash-subgraph/stdlib/subgraph/roots"; +import { getRoots } from "@local/hash-subgraph/stdlib"; import { Box } from "@mui/material"; import { useMemo } from "react"; @@ -33,13 +33,11 @@ import { } from "./links-section/shared/table-styling"; export const ClaimsSection = () => { - const { entitySubgraph, onEntityClick } = useEntityEditor(); + const { entity, onEntityClick, isLocalDraftOnly } = useEntityEditor(); const entityUuid = useMemo(() => { - return extractEntityUuidFromEntityId( - getRoots(entitySubgraph)[0]!.metadata.recordId.entityId, - ); - }, [entitySubgraph]); + return extractEntityUuidFromEntityId(entity.metadata.recordId.entityId); + }, [entity]); const { data: claimsData } = useQuery< GetEntitySubgraphQuery, @@ -75,7 +73,7 @@ export const ClaimsSection = () => { includeDrafts: true, }, }, - skip: !entityUuid, + skip: !entityUuid || isLocalDraftOnly, fetchPolicy: "cache-and-network", }); @@ -109,6 +107,10 @@ export const ClaimsSection = () => { 16, ); + if (isLocalDraftOnly) { + return null; + } + return ( ; interface Props extends EntityEditorProps { + entity: Entity; + isLocalDraftOnly: boolean; propertyExpandStatus: TableExpandStatus; togglePropertyExpand: (id: string) => void; } @@ -50,9 +55,11 @@ export const EntityEditorContextProvider = ({ }); }, []); - useMemo(() => { + const entity = useMemo(() => { const roots = getRoots(entitySubgraph); + const foundEntity = roots[0]; + if (roots.length > 1) { /** * This is an early warning system in case we accidentally start passing a subgraph with multiple roots to the entity editor. @@ -69,6 +76,12 @@ export const EntityEditorContextProvider = ({ .join(", ")}`, ); } + + if (!foundEntity) { + throw new Error("No root entity found in entity editor subgraph"); + } + + return foundEntity; }, [entitySubgraph]); const state = useMemo( @@ -81,10 +94,14 @@ export const EntityEditorContextProvider = ({ disableTypeClick, draftLinksToArchive, draftLinksToCreate, + entity, entityLabel, entitySubgraph, handleTypesChange, isDirty, + isLocalDraftOnly: + extractEntityUuidFromEntityId(entity.metadata.recordId.entityId) === + "draft", onEntityClick, onEntityUpdated, propertyExpandStatus, @@ -104,6 +121,7 @@ export const EntityEditorContextProvider = ({ disableTypeClick, draftLinksToArchive, draftLinksToCreate, + entity, entityLabel, entitySubgraph, handleTypesChange, diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/file-preview-section.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/file-preview-section.tsx index ff4fbc4281b..65f45dc0108 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/file-preview-section.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/file-preview-section.tsx @@ -12,7 +12,6 @@ import { generateEntityLabel } from "@local/hash-isomorphic-utils/generate-entit import { simplifyProperties } from "@local/hash-isomorphic-utils/simplify-properties"; import type { FileProperties } from "@local/hash-isomorphic-utils/system-types/shared"; import { extractOwnedByIdFromEntityId } from "@local/hash-subgraph"; -import { getRoots } from "@local/hash-subgraph/stdlib"; import { Box, CircularProgress, @@ -62,12 +61,10 @@ const ReplaceFile = ({ isImage: boolean; close: () => void; }) => { - const { entitySubgraph, onEntityUpdated } = useEntityEditor(); + const { entity, onEntityUpdated } = useEntityEditor(); const { refetch: refetchUser } = useAuthInfo(); const [fileBeingUploaded, setFileBeingUploaded] = useState(null); - const entity = getRoots(entitySubgraph)[0]!; - const { uploadFile, uploads } = useFileUploads(); const uploadsProgress = useFileUploadsProgress(); @@ -170,9 +167,8 @@ export const FilePreviewSection = () => { const [showSearch, setShowSearch] = useState(false); const [showThumbnails, setShowThumbnails] = useState(true); - const { isDirty, readonly, entitySubgraph } = useEntityEditor(); - - const entity = getRoots(entitySubgraph)[0]!; + const { isDirty, readonly, closedMultiEntityType, entity } = + useEntityEditor(); const { isImage, fileUrl } = getFileProperties(entity.properties); @@ -184,7 +180,8 @@ export const FilePreviewSection = () => { entity.properties as FileProperties, ); - const title = displayName ?? generateEntityLabel(entitySubgraph, entity); + const title = + displayName ?? generateEntityLabel(closedMultiEntityType, entity); const alt = description ?? title; diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/history-section/get-history-events.ts b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/history-section/get-history-events.ts index f8459cb791d..dba4cd30d70 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/history-section/get-history-events.ts +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/history-section/get-history-events.ts @@ -3,6 +3,7 @@ import { extractVersion } from "@blockprotocol/type-system/slim"; import { typedEntries } from "@local/advanced-types/typed-entries"; import type { EntityTypeIdDiff } from "@local/hash-graph-client"; import type { EntityId, PropertyPath } from "@local/hash-graph-types/entity"; +import { isValueMetadata } from "@local/hash-graph-types/entity"; import type { BaseUrl, EntityTypeWithMetadata, @@ -153,13 +154,19 @@ export const getHistoryEvents = (diffs: EntityDiff[], subgraph: Subgraph) => { if (diffData.diff.properties) { for (const propertyDiff of diffData.diff.properties) { - const propertyProvenance = changedEntityEdition.propertyMetadata( + const propertyMetadata = changedEntityEdition.propertyMetadata( propertyDiff.path as PropertyPath, - )?.provenance; + ); + + if (!propertyMetadata || !isValueMetadata(propertyMetadata)) { + /** + * @todo H-2775 – handle property objects and changes to array contents + */ + continue; + } + + const propertyProvenance = propertyMetadata.metadata.provenance; - /** - * @todo H-2775 – handle property objects and changes to array contents - */ const propertyBaseUrl = propertyDiff.path[0] as BaseUrl; try { const propertyTypeWithMetadata = getPropertyTypeForEntity( @@ -207,12 +214,16 @@ export const getHistoryEvents = (diffs: EntityDiff[], subgraph: Subgraph) => { for (const [index, [key, value]] of typedEntries( firstEntityEdition.properties, ).entries()) { - /** - * @todo H-2775 – handle property objects and changes to array contents - */ - const propertyProvenance = firstEntityEdition.propertyMetadata([ - key, - ])?.provenance; + const propertyMetadata = firstEntityEdition.propertyMetadata([key]); + + if (!propertyMetadata || !isValueMetadata(propertyMetadata)) { + /** + * @todo H-2775 – handle property objects and changes to array contents + */ + continue; + } + + const propertyProvenance = propertyMetadata.metadata.provenance; try { const propertyTypeWithMetadata = getPropertyTypeForEntity( diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/link-section.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/link-section.tsx index 16b49d625fd..a9abc9fe370 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/link-section.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/link-section.tsx @@ -1,5 +1,4 @@ import { LinkEntity } from "@local/hash-graph-sdk/entity"; -import { getRoots } from "@local/hash-subgraph/stdlib"; import type { FunctionComponent } from "react"; import { useMemo } from "react"; @@ -11,19 +10,14 @@ export const LinkSection: FunctionComponent = () => { const { closedMultiEntityTypesMap, closedMultiEntityTypesDefinitions, + entity, entitySubgraph, onEntityClick, } = useEntityEditor(); const linkEntity = useMemo(() => { - const [rootEntity] = getRoots(entitySubgraph); - - if (!rootEntity) { - throw new Error("No root entity found in entity editor subgraph."); - } - - return new LinkEntity(rootEntity); - }, [entitySubgraph]); + return new LinkEntity(entity); + }, [entity]); return ( diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section.tsx index 1c1eb7344f8..1452b4edde7 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section.tsx @@ -4,7 +4,6 @@ import { systemEntityTypes } from "@local/hash-isomorphic-utils/ontology-type-id import { getIncomingLinkAndSourceEntities, getOutgoingLinksForEntity, - getRoots, } from "@local/hash-subgraph/stdlib"; import { Stack } from "@mui/material"; @@ -13,9 +12,7 @@ import { IncomingLinksSection } from "./links-section/incoming-links-section"; import { OutgoingLinksSection } from "./links-section/outgoing-links-section"; export const LinksSection = ({ isLinkEntity }: { isLinkEntity: boolean }) => { - const { draftLinksToArchive, entitySubgraph } = useEntityEditor(); - - const entity = getRoots(entitySubgraph)[0]!; + const { draftLinksToArchive, entity, entitySubgraph } = useEntityEditor(); const outgoingLinks = getOutgoingLinksForEntity( entitySubgraph, diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section.tsx index f668106442e..ac6faaa9df7 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section.tsx @@ -2,10 +2,7 @@ import { faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons"; import { Chip, FontAwesomeIcon, IconButton } from "@hashintel/design-system"; import type { Entity } from "@local/hash-graph-sdk/entity"; import type { EntityProperties } from "@local/hash-graph-types/entity"; -import { - getOutgoingLinkAndTargetEntities, - getRoots, -} from "@local/hash-subgraph/stdlib"; +import { getOutgoingLinkAndTargetEntities } from "@local/hash-subgraph/stdlib"; import { Paper, Stack } from "@mui/material"; import { useState } from "react"; @@ -33,7 +30,7 @@ export const OutgoingLinksSection = ({ }: OutgoingLinksSectionPropsProps) => { const [showSearch, setShowSearch] = useState(false); - const { entitySubgraph, readonly } = useEntityEditor(); + const { entitySubgraph, entity, readonly } = useEntityEditor(); const rows = useRows(); const createGetCellContent = useCreateGetCellContent(); @@ -47,8 +44,6 @@ export const OutgoingLinksSection = ({ return null; } - const entity = getRoots(entitySubgraph)[0]!; - const outgoingLinksAndTargets = readonly ? getOutgoingLinkAndTargetEntities( entitySubgraph, diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/cells/linked-with-cell/linked-entity-list-editor.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/cells/linked-with-cell/linked-entity-list-editor.tsx index 7ea7446439d..c8adf45c067 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/cells/linked-with-cell/linked-entity-list-editor.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/cells/linked-with-cell/linked-entity-list-editor.tsx @@ -12,7 +12,6 @@ import type { Timestamp, } from "@local/hash-graph-types/temporal-versioning"; import { extractDraftIdFromEntityId } from "@local/hash-subgraph"; -import { getRoots } from "@local/hash-subgraph/stdlib"; import { Box } from "@mui/material"; import produce from "immer"; import { useMemo, useState } from "react"; @@ -82,7 +81,7 @@ export const createDraftLinkEntity = ({ export const LinkedEntityListEditor: ProvideEditorComponent = ( props, ) => { - const { entitySubgraph, setDraftLinksToCreate, readonly } = useEntityEditor(); + const { entity, setDraftLinksToCreate, readonly } = useEntityEditor(); const markLinkEntityToArchive = useMarkLinkEntityToArchive(); const { value: cell, onFinishedEditing, onChange } = props; @@ -96,8 +95,6 @@ export const LinkedEntityListEditor: ProvideEditorComponent = ( const [addingLink, setAddingLink] = useState(!linkAndTargetEntities.length); - const entity = useMemo(() => getRoots(entitySubgraph)[0]!, [entitySubgraph]); - const onSelect = (selectedEntity: Entity, entityLabel: string) => { const alreadyLinked = linkAndTargetEntities.find( ({ rightEntity }) => diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/cells/linked-with-cell/linked-entity-selector.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/cells/linked-with-cell/linked-entity-selector.tsx index 944fbefa977..1fe0431601e 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/cells/linked-with-cell/linked-entity-selector.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/cells/linked-with-cell/linked-entity-selector.tsx @@ -5,7 +5,6 @@ import type { Entity } from "@local/hash-graph-sdk/entity"; import { getClosedMultiEntityTypeFromMap } from "@local/hash-graph-sdk/entity"; import type { EntityId } from "@local/hash-graph-types/entity"; import { generateEntityLabel } from "@local/hash-isomorphic-utils/generate-entity-label"; -import { getRoots } from "@local/hash-subgraph/stdlib"; import type { PaperProps } from "@mui/material"; import { Stack, Typography } from "@mui/material"; import { @@ -69,10 +68,9 @@ export const LinkedEntitySelector = ({ entityIdsToFilterOut, linkEntityTypeId, }: LinkedEntitySelectorProps) => { - const { entitySubgraph, readonly } = useEntityEditor(); + const { entity, readonly } = useEntityEditor(); - const entityId = getRoots(entitySubgraph)[0]?.metadata.recordId - .entityId as EntityId; + const entityId = entity.metadata.recordId.entityId; const [showUploadFileMenu, setShowUploadFileMenu] = useState(false); @@ -127,13 +125,13 @@ export const LinkedEntitySelector = ({ }, makePublic: false, onComplete: (upload) => { - const entity = upload.createdEntities.fileEntity; + const fileEntity = upload.createdEntities.fileEntity; const label = - entity.properties[ + fileEntity.properties[ "https://blockprotocol.org/@blockprotocol/types/property-type/display-name/" ] ?? - entity.properties[ + fileEntity.properties[ "https://blockprotocol.org/@blockprotocol/types/property-type/file-name/" ] ?? "File"; @@ -183,15 +181,15 @@ export const LinkedEntitySelector = ({ expectedEntityTypes={expectedEntityTypes} includeDrafts={includeDrafts} multiple={false} - onSelect={(entity, closedMultiEntityTypeMap) => { + onSelect={(selectedEntity, closedMultiEntityTypeMap) => { const closedType = getClosedMultiEntityTypeFromMap( closedMultiEntityTypeMap, - entity.metadata.entityTypeIds, + selectedEntity.metadata.entityTypeIds, ); - const label = generateEntityLabel(closedType, entity); + const label = generateEntityLabel(closedType, selectedEntity); - onSelect(entity, label); + onSelect(selectedEntity, label); }} className={GRID_CLICK_IGNORE_CLASS} open diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/cells/linked-with-cell/linked-with-cell-editor.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/cells/linked-with-cell/linked-with-cell-editor.tsx index 61bd0da2d93..73ac80b50d7 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/cells/linked-with-cell/linked-with-cell-editor.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/cells/linked-with-cell/linked-with-cell-editor.tsx @@ -1,8 +1,6 @@ import type { ProvideEditorComponent } from "@glideapps/glide-data-grid"; import type { Entity } from "@local/hash-graph-sdk/entity"; import { extractDraftIdFromEntityId } from "@local/hash-subgraph"; -import { getRoots } from "@local/hash-subgraph/stdlib"; -import { useMemo } from "react"; import { useMarkLinkEntityToArchive } from "../../../../../shared/use-mark-link-entity-to-archive"; import { useEntityEditor } from "../../../../entity-editor-context"; @@ -16,7 +14,7 @@ import { LinkedEntitySelector } from "./linked-entity-selector"; export const LinkedWithCellEditor: ProvideEditorComponent = ( props, ) => { - const { entitySubgraph, setDraftLinksToCreate } = useEntityEditor(); + const { entity, setDraftLinksToCreate } = useEntityEditor(); const markLinkEntityToArchive = useMarkLinkEntityToArchive(); const { value: cell, onFinishedEditing } = props; @@ -28,8 +26,6 @@ export const LinkedWithCellEditor: ProvideEditorComponent = ( maxItems, } = cell.data.linkRow; - const entity = useMemo(() => getRoots(entitySubgraph)[0]!, [entitySubgraph]); - const onSelectForSingleLink = ( selectedEntity: Entity, selectedEntityLabel: string, diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/use-rows.ts b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/use-rows.ts index 7cda40f163b..97e5edec575 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/use-rows.ts +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/links-section/outgoing-links-section/use-rows.ts @@ -5,7 +5,6 @@ import type { PartialEntityType } from "@local/hash-graph-types/ontology"; import { generateEntityLabel } from "@local/hash-isomorphic-utils/generate-entity-label"; import { getOutgoingLinkAndTargetEntities, - getRoots, intervalCompareWithInterval, } from "@local/hash-subgraph/stdlib"; import { useMemo } from "react"; @@ -21,6 +20,7 @@ export const useRows = () => { closedMultiEntityType, closedMultiEntityTypesDefinitions, closedMultiEntityTypesMap, + entity, entitySubgraph, draftLinksToArchive, draftLinksToCreate, @@ -34,8 +34,6 @@ export const useRows = () => { const { uploads, uploadFile } = useFileUploads(); const rows = useMemo(() => { - const entity = getRoots(entitySubgraph)[0]!; - const variableAxis = entitySubgraph.temporalAxes.resolved.variable.axis; const entityInterval = entity.metadata.temporalVersioning[variableAxis]; @@ -213,6 +211,7 @@ export const useRows = () => { closedMultiEntityTypesMap, closedMultiEntityTypesDefinitions, entitySubgraph, + entity, draftLinksToArchive, draftLinksToCreate, isSpecialEntityTypeLookup, diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/change-type-cell.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/change-type-cell.tsx index a88865de296..495fd5578fb 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/change-type-cell.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/change-type-cell.tsx @@ -6,6 +6,7 @@ import type { } from "@glideapps/glide-data-grid"; import { GridCellKind } from "@glideapps/glide-data-grid"; import { customColors } from "@hashintel/design-system/theme"; +import { getMergedDataTypeSchema } from "@local/hash-isomorphic-utils/data-types"; import produce from "immer"; import type { RefObject } from "react"; @@ -17,7 +18,6 @@ import { propertyGridIndexes } from "../constants"; import type { PropertyRow } from "../types"; import { getEditorSpecs } from "./value-cell/editor-specs"; import type { ValueCell } from "./value-cell/types"; -import { guessEditorTypeFromExpectedType } from "./value-cell/utils"; export interface ChangeTypeCellProps { readonly kind: "change-type-cell"; @@ -53,10 +53,15 @@ export const createRenderChangeTypeCell = ( ctx.font = changeTextFont; const changeTextWidth = ctx.measureText(changeText).width; - const editorSpec = getEditorSpecs( - guessEditorTypeFromExpectedType(currentType), - currentType, - ); + const schema = getMergedDataTypeSchema(currentType); + + if ("anyOf" in schema) { + throw new Error( + "Data types with different expected sets of constraints (anyOf) are not yet supported", + ); + } + + const editorSpec = getEditorSpecs(currentType, schema); const drawTheLeftChip = () => drawChipWithIcon({ @@ -110,6 +115,7 @@ export const createRenderChangeTypeCell = ( const newContent = produce(valueCellOfThisRow, (draft) => { draft.data.propertyRow.value = undefined; + draft.data.propertyRow.valueMetadata = undefined; }); /** diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell.tsx index 3fa17f3cc17..2fe53fc6252 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell.tsx @@ -1,10 +1,16 @@ import type { JsonValue } from "@blockprotocol/core"; -import type { ClosedDataType } from "@blockprotocol/type-system"; import type { CustomCell, CustomRenderer } from "@glideapps/glide-data-grid"; import { GridCellKind } from "@glideapps/glide-data-grid"; import { customColors } from "@hashintel/design-system/theme"; +import { + isArrayMetadata, + isValueMetadata, +} from "@local/hash-graph-types/entity"; import type { FormattedValuePart } from "@local/hash-isomorphic-utils/data-types"; -import { formatDataValue } from "@local/hash-isomorphic-utils/data-types"; +import { + formatDataValue, + getMergedDataTypeSchema, +} from "@local/hash-isomorphic-utils/data-types"; import { getCellHorizontalPadding, @@ -17,40 +23,9 @@ import { InteractableManager } from "../../../../../../../../components/grid/uti import { drawInteractableTooltipIcons } from "../../../../../../../../components/grid/utils/use-grid-tooltip/draw-interactable-tooltip-icons"; import { isValueEmpty } from "../../is-value-empty"; import { ArrayEditor } from "./value-cell/array-editor"; -import { getEditorSpecs } from "./value-cell/editor-specs"; import { ReadonlyPopup } from "./value-cell/readonly-popup"; import { SingleValueEditor } from "./value-cell/single-value-editor"; import type { ValueCell } from "./value-cell/types"; -import { guessEditorTypeFromValue } from "./value-cell/utils"; - -const guessDataTypeFromValue = ( - value: JsonValue, - expectedTypes: ClosedDataType[], -) => { - const editorType = guessEditorTypeFromValue(value, expectedTypes); - - const expectedType = expectedTypes.find(({ allOf }) => - allOf.some((constraint) => - "type" in constraint - ? constraint.type === editorType - : /** - * @todo H-3374 support anyOf in expected types. also don't need to guess the value any more, use dataTypeId - * from property metadata - */ - constraint.anyOf.some((subType) => subType.type === editorType), - ), - ); - - if (!expectedType) { - throw new Error( - `Could not find guessed editor type ${editorType} among expected types ${expectedTypes - .map((opt) => opt.$id) - .join(", ")}`, - ); - } - - return expectedType; -}; export const renderValueCell: CustomRenderer = { kind: GridCellKind.Custom, @@ -62,7 +37,7 @@ export const renderValueCell: CustomRenderer = { const { readonly } = cell.data; - const { value, permittedDataTypes, isArray, isSingleUrl } = + const { value, valueMetadata, permittedDataTypes, isArray, isSingleUrl } = cell.data.propertyRow; ctx.fillStyle = theme.textHeader; @@ -71,52 +46,23 @@ export const renderValueCell: CustomRenderer = { const yCenter = getYCenter(args); const left = rect.x + getCellHorizontalPadding(); - const editorType = guessEditorTypeFromValue(value, permittedDataTypes); - const relevantType = permittedDataTypes.find(({ allOf }) => - allOf.some((constraint) => - "type" in constraint - ? constraint.type === editorType - : /** - * @todo H-3374 support anyOf in expected types. also don't need to guess the value any more, use dataTypeId - * from property metadata - */ - constraint.anyOf.some((subType) => subType.type === editorType), - ), - ); - - const editorSpec = getEditorSpecs(editorType, relevantType); - if (isValueEmpty(value)) { // draw empty value ctx.fillStyle = customColors.gray[50]; ctx.font = "italic 14px Inter"; const emptyText = isArray ? "No values" : "No value"; ctx.fillText(emptyText, left, yCenter); - } else if (!isArray && editorSpec.shouldBeDrawnAsAChip) { - const expectedType = guessDataTypeFromValue( - value as JsonValue, - permittedDataTypes, - ); - + } else if (!isArray && typeof value === "object") { drawChipWithText({ args, left, - text: formatDataValue(value as JsonValue, expectedType) - .map((part) => part.text) - .join(""), + text: !value ? "null" : JSON.stringify(value), }); - } else if (editorType === "boolean") { - const expectedType = guessDataTypeFromValue( - value as JsonValue, - permittedDataTypes, - ); - + } else if (typeof value === "boolean") { // draw boolean drawTextWithIcon({ args, - text: formatDataValue(value as JsonValue, expectedType) - .map((part) => part.text) - .join(""), + text: value.toString(), icon: value ? "bpCheck" : "bpCross", left, iconColor: customColors.gray[50], @@ -125,14 +71,56 @@ export const renderValueCell: CustomRenderer = { } else if (readonly && isSingleUrl) { drawUrlAsLink({ args, url: value as string, left }); } else { + if (!valueMetadata) { + throw new Error( + `Expected value metadata to be set when value '${value}' is not empty`, + ); + } + const valueParts: FormattedValuePart[] = []; if (Array.isArray(value)) { for (const [index, entry] of value.entries()) { - const expectedType = guessDataTypeFromValue( - entry as JsonValue, - permittedDataTypes, + if (!isArrayMetadata(valueMetadata)) { + throw new Error( + `Expected array metadata for value '${JSON.stringify(value)}', got ${JSON.stringify(valueMetadata)}`, + ); + } + + const arrayItemMetadata = valueMetadata.value[index]; + + if (!arrayItemMetadata) { + throw new Error( + `Expected metadata for array item at index ${index} in value '${JSON.stringify(value)}'`, + ); + } + + if (!isValueMetadata(arrayItemMetadata)) { + throw new Error( + `Expected single value metadata for array item at index ${index} in value '${JSON.stringify(value)}', got ${JSON.stringify(arrayItemMetadata)}`, + ); + } + + const dataTypeId = arrayItemMetadata.metadata.dataTypeId; + + const dataType = permittedDataTypes.find( + (type) => type.$id === dataTypeId, ); - valueParts.push(...formatDataValue(entry as JsonValue, expectedType)); + + if (!dataType) { + throw new Error( + "Expected a data type to be set on the value or at least one permitted data type", + ); + } + + const schema = getMergedDataTypeSchema(dataType); + + if ("anyOf" in schema) { + throw new Error( + "Data types with different expected sets of constraints (anyOf) are not yet supported", + ); + } + + valueParts.push(...formatDataValue(entry as JsonValue, schema)); if (index < value.length - 1) { valueParts.push({ text: ", ", @@ -142,11 +130,33 @@ export const renderValueCell: CustomRenderer = { } } } else { - const expectedType = guessDataTypeFromValue( - value as JsonValue, - permittedDataTypes, + if (!isValueMetadata(valueMetadata)) { + throw new Error( + `Expected single value metadata for value '${value}', got ${JSON.stringify(valueMetadata)}`, + ); + } + + const dataTypeId = valueMetadata.metadata.dataTypeId; + + const dataType = permittedDataTypes.find( + (type) => type.$id === dataTypeId, ); - valueParts.push(...formatDataValue(value as JsonValue, expectedType)); + + if (!dataType) { + throw new Error( + "Expected a data type to be set on the value or at least one permitted data type", + ); + } + + const schema = getMergedDataTypeSchema(dataType); + + if ("anyOf" in schema) { + throw new Error( + "Data types with different expected sets of constraints (anyOf) are not yet supported", + ); + } + + valueParts.push(...formatDataValue(value as JsonValue, schema)); } let textOffset = left; diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/array-editor.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/array-editor.tsx index e44bc419081..42415a8f202 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/array-editor.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/array-editor.tsx @@ -1,3 +1,4 @@ +import type { VersionedUrl } from "@blockprotocol/type-system/slim"; import type { DragEndEvent } from "@dnd-kit/core"; import { closestCenter, @@ -13,6 +14,12 @@ import { sortableKeyboardCoordinates, verticalListSortingStrategy, } from "@dnd-kit/sortable"; +import type { PropertyMetadataArray } from "@local/hash-graph-types/entity"; +import { + isArrayMetadata, + isValueMetadata, +} from "@local/hash-graph-types/entity"; +import { getMergedDataTypeSchema } from "@local/hash-isomorphic-utils/data-types"; import { Box, styled } from "@mui/material"; import produce from "immer"; import { isNumber } from "lodash"; @@ -26,10 +33,7 @@ import { SortableRow } from "./array-editor/sortable-row"; import type { SortableItem } from "./array-editor/types"; import { getEditorSpecs } from "./editor-specs"; import type { ValueCellEditorComponent } from "./types"; -import { - guessEditorTypeFromExpectedType, - isBlankStringOrNullish, -} from "./utils"; +import { isBlankStringOrNullish } from "./utils"; export const DRAFT_ROW_KEY = "draft"; @@ -50,7 +54,10 @@ export const ArrayEditor: ValueCellEditorComponent = ({ const listWrapperRef = useRef(null); const { value: propertyValue, + valueMetadata, + generateNewMetadataObject, permittedDataTypes, + propertyKeyChain, maxItems, minItems, } = cell.data.propertyRow; @@ -58,14 +65,55 @@ export const ArrayEditor: ValueCellEditorComponent = ({ const items = useMemo(() => { const values = Array.isArray(propertyValue) ? propertyValue : []; - const itemsArray: SortableItem[] = values.map((value, index) => ({ - index, - id: `${index}_${String(value)}`, - value, - })); + if (values.length && !valueMetadata) { + throw new Error("Expected valueMetadata to be set when there are values"); + } + + if (valueMetadata && !isArrayMetadata(valueMetadata)) { + throw new Error( + `Expected array metadata for value '${JSON.stringify(values)}', got ${JSON.stringify(valueMetadata)}`, + ); + } + + const itemsArray: SortableItem[] = values.map((value, index) => { + const arrayItemMetadata = (valueMetadata as PropertyMetadataArray).value[ + index + ]; + + if (!arrayItemMetadata) { + throw new Error( + `Expected metadata for array item at index ${index} in value '${JSON.stringify(value)}'`, + ); + } + + if (!isValueMetadata(arrayItemMetadata)) { + throw new Error( + `Expected single value metadata for array item at index ${index} in value '${JSON.stringify(value)}', got ${JSON.stringify(arrayItemMetadata)}`, + ); + } + + const dataTypeId = arrayItemMetadata.metadata.dataTypeId; + + const dataType = permittedDataTypes.find( + (type) => type.$id === dataTypeId, + ); + + if (!dataType) { + throw new Error( + "Expected a data type to be set on the value or at least one permitted data type", + ); + } + + return { + dataType, + index, + id: `${index}_${String(value)}`, + value, + }; + }); return itemsArray; - }, [propertyValue]); + }, [propertyValue, valueMetadata, permittedDataTypes]); const [selectedRow, setSelectedRow] = useState(""); const [editingRow, setEditingRow] = useState(() => { @@ -75,11 +123,20 @@ export const ArrayEditor: ValueCellEditorComponent = ({ } if (permittedDataTypes.length === 1) { - const expectedType = guessEditorTypeFromExpectedType( - permittedDataTypes[0]!, - ); + const expectedType = permittedDataTypes[0]!; + + const schema = getMergedDataTypeSchema(expectedType); - if (getEditorSpecs(expectedType).arrayEditException === "no-edit-mode") { + if ("anyOf" in schema) { + throw new Error( + "Data types with different expected sets of constraints (anyOf) are not yet supported", + ); + } + + if ( + getEditorSpecs(expectedType, schema).arrayEditException === + "no-edit-mode" + ) { return ""; } } @@ -91,14 +148,22 @@ export const ArrayEditor: ValueCellEditorComponent = ({ setSelectedRow((prevId) => (id === prevId ? "" : id)); }; - const addItem = (value: unknown) => { + const addItem = (value: unknown, dataTypeId: VersionedUrl) => { setEditingRow(""); + const { propertyMetadata } = generateNewMetadataObject({ + propertyKeyChain, + valuePath: [...propertyKeyChain, items.length], + valueMetadata: { metadata: { dataTypeId } }, + }); + const newCell = produce(cell, (draftCell) => { draftCell.data.propertyRow.value = [ ...items.map((item) => item.value), value, ]; + + draftCell.data.propertyRow.valueMetadata = propertyMetadata; }); onChange(newCell); @@ -111,11 +176,20 @@ export const ArrayEditor: ValueCellEditorComponent = ({ }; const removeItem = (indexToRemove: number) => { + const { propertyMetadata } = generateNewMetadataObject({ + propertyKeyChain, + valuePath: [...propertyKeyChain, indexToRemove], + valueMetadata: "delete", + }); + const newCell = produce(cell, (draftCell) => { draftCell.data.propertyRow.value = items .filter((_, index) => indexToRemove !== index) .map(({ value }) => value); + + draftCell.data.propertyRow.valueMetadata = propertyMetadata; }); + onChange(newCell); }; @@ -137,6 +211,25 @@ export const ArrayEditor: ValueCellEditorComponent = ({ const newItems = arrayMove(items, oldIndex, newIndex); draftCell.data.propertyRow.value = newItems.map(({ value }) => value); + + if (!valueMetadata) { + throw new Error( + "Expected valueMetadata to be set when there are values", + ); + } + + if (!isArrayMetadata(valueMetadata)) { + throw new Error( + `Expected array metadata for value '${JSON.stringify(newItems)}', got ${JSON.stringify(valueMetadata)}`, + ); + } + + const newMetadata = arrayMove(valueMetadata.value, oldIndex, newIndex); + + draftCell.data.propertyRow.valueMetadata = { + ...valueMetadata, + value: newMetadata, + }; }); onChange(newCell); }; @@ -163,13 +256,22 @@ export const ArrayEditor: ValueCellEditorComponent = ({ const onlyOneExpectedType = permittedDataTypes.length === 1; const expectedType = permittedDataTypes[0]!; - const editorType = guessEditorTypeFromExpectedType(expectedType); - const editorSpec = getEditorSpecs(editorType, expectedType); + + const schema = getMergedDataTypeSchema(expectedType); + + if ("anyOf" in schema) { + throw new Error( + "Data types with different expected sets of constraints (anyOf) are not yet supported", + ); + } + + const editorSpec = getEditorSpecs(expectedType, schema); + const noEditMode = editorSpec.arrayEditException === "no-edit-mode"; // add the value on click instead of showing draftRow if (onlyOneExpectedType && noEditMode) { - return addItem(editorSpec.defaultValue); + return addItem(editorSpec.defaultValue, expectedType.$id); } setEditingRow(DRAFT_ROW_KEY); diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/array-editor/draft-row.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/array-editor/draft-row.tsx index 7bd37779d02..904c0a08626 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/array-editor/draft-row.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/array-editor/draft-row.tsx @@ -1,20 +1,15 @@ -import type { ClosedDataType } from "@blockprotocol/type-system"; +import type { ClosedDataType, VersionedUrl } from "@blockprotocol/type-system"; import { useState } from "react"; import { DRAFT_ROW_KEY } from "../array-editor"; -import { getEditorSpecs } from "../editor-specs"; import { EditorTypePicker } from "../editor-type-picker"; -import type { EditorType } from "../types"; -import { - guessEditorTypeFromExpectedType, - isBlankStringOrNullish, -} from "../utils"; +import { isBlankStringOrNullish } from "../utils"; import { SortableRow } from "./sortable-row"; interface DraftRowProps { expectedTypes: ClosedDataType[]; existingItemCount: number; - onDraftSaved: (value: unknown) => void; + onDraftSaved: (value: unknown, dataTypeId: VersionedUrl) => void; onDraftDiscarded: () => void; } @@ -24,7 +19,7 @@ export const DraftRow = ({ onDraftSaved, onDraftDiscarded, }: DraftRowProps) => { - const [editorType, setEditorType] = useState(() => { + const [dataType, setDataType] = useState(() => { if (expectedTypes.length > 1) { return null; } @@ -33,21 +28,15 @@ export const DraftRow = ({ throw new Error("there is no expectedType found on property type"); } - return guessEditorTypeFromExpectedType(expectedTypes[0]); + return expectedTypes[0]; }); - if (!editorType) { + if (!dataType) { return ( { - const editorSpec = getEditorSpecs(type); - - if (editorSpec.arrayEditException === "no-edit-mode") { - onDraftSaved(editorSpec.defaultValue); - } - - setEditorType(type); + setDataType(type); }} /> ); @@ -57,17 +46,17 @@ export const DraftRow = ({ { if (isBlankStringOrNullish(value)) { return onDraftDiscarded(); } - onDraftSaved(value); + onDraftSaved(value, dataType.$id); }} onDiscardChanges={onDraftDiscarded} expectedTypes={expectedTypes} diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/array-editor/sortable-row.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/array-editor/sortable-row.tsx index 234fe7b46c4..a1c356e78b7 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/array-editor/sortable-row.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/array-editor/sortable-row.tsx @@ -1,8 +1,5 @@ import type { JsonValue } from "@blockprotocol/core"; -import type { - ClosedDataType, - ValueConstraints, -} from "@blockprotocol/type-system"; +import type { ClosedDataType } from "@blockprotocol/type-system"; import { useSortable } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import { @@ -11,16 +8,18 @@ import { faPencil, faTrash, } from "@fortawesome/free-solid-svg-icons"; -import { formatDataValue } from "@local/hash-isomorphic-utils/data-types"; +import { + formatDataValue, + getMergedDataTypeSchema, +} from "@local/hash-isomorphic-utils/data-types"; import DragIndicatorIcon from "@mui/icons-material/DragIndicator"; -import { Box, Divider, Typography } from "@mui/material"; +import { Box, Divider, Stack, Typography } from "@mui/material"; import { useRef, useState } from "react"; import { getEditorSpecs } from "../editor-specs"; import { BooleanInput } from "../inputs/boolean-input"; import { JsonInput } from "../inputs/json-input"; import { NumberOrTextInput } from "../inputs/number-or-text-input"; -import { guessEditorTypeFromValue } from "../utils"; import { RowAction } from "./row-action"; import type { SortableItem } from "./types"; import { ValueChip } from "./value-chip"; @@ -46,9 +45,8 @@ export const SortableRow = ({ onSaveChanges, onDiscardChanges, editing, - expectedTypes, }: SortableRowProps) => { - const { id, value, index, overriddenEditorType } = item; + const { id, value, index, dataType } = item; const { attributes, isDragging, @@ -66,46 +64,15 @@ export const SortableRow = ({ const [draftValue, setDraftValue] = useState(value); const [prevEditing, setPrevEditing] = useState(editing); - const editorType = - overriddenEditorType ?? guessEditorTypeFromValue(value, expectedTypes); - - let valueConstraints: ValueConstraints; + const schema = getMergedDataTypeSchema(dataType); - /** @todo H-3374 don't guess the type, take it from the data type metadata */ - /* eslint-disable no-labels */ - outerLoop: for (const expectedType of expectedTypes) { - for (const constraint of expectedType.allOf) { - if ("type" in constraint) { - if (constraint.type === editorType) { - valueConstraints = constraint; - break outerLoop; - } - } else { - for (const innerConstraint of constraint.anyOf) { - if ("type" in innerConstraint) { - if (innerConstraint.type === editorType) { - valueConstraints = innerConstraint; - break outerLoop; - } - } - } - } - } + if ("anyOf" in schema) { + throw new Error( + "Data types with different expected sets of constraints (anyOf) are not yet supported", + ); } - /* eslint-enable no-labels */ - - const expectedType = expectedTypes.find((type) => - type.allOf.some((constraint) => - "type" in constraint - ? constraint.type === editorType - : /** - * @todo H-3374 support multiple expected data types - */ - constraint.anyOf.some((subType) => subType.type === editorType), - ), - ); - const editorSpec = getEditorSpecs(editorType, expectedType); + const editorSpec = getEditorSpecs(dataType, schema); const { arrayEditException } = editorSpec; @@ -120,7 +87,7 @@ export const SortableRow = ({ const textInputFormRef = useRef(null); const saveChanges = () => { - if (!["object", "boolean"].includes(editorType)) { + if (!["object", "boolean"].includes(schema.type)) { /** * We want form validation triggered when the user tries to add a text or number value */ @@ -131,7 +98,7 @@ export const SortableRow = ({ }; const renderEditor = () => { - if (editorType === "boolean") { + if (schema.type === "boolean") { return ( opt.$id) - .join(", ")}`, - ); - } - return ( ); }; const renderValue = () => { - if (editorType === "boolean") { + if (schema.type === "boolean") { return ( opt.$id) - .join(", ")}`, - ); - } - return ( part.text) - .join("")} + title={ + + {formatDataValue(value as JsonValue, schema).map((part, idx) => ( + // eslint-disable-next-line react/no-array-index-key + + {part.text} + + ))} + + } selected={!!selected} icon={{ icon: editorSpec.icon }} - tooltip={expectedType.title} + tooltip={dataType.title} /> ); }; diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/array-editor/types.ts b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/array-editor/types.ts index 50480aee01b..7465e262f89 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/array-editor/types.ts +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/array-editor/types.ts @@ -1,9 +1,8 @@ -import type { EditorType } from "../types"; +import type { ClosedDataType } from "@blockprotocol/type-system"; export interface SortableItem { value: unknown; id: string; index: number; - // if overriddenEditorType is defined, sortable item's editor type will be this editor type - overriddenEditorType?: EditorType; + dataType: ClosedDataType; } diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/array-editor/value-chip.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/array-editor/value-chip.tsx index 5cbd3194d06..222e50d18fc 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/array-editor/value-chip.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/array-editor/value-chip.tsx @@ -2,6 +2,7 @@ import type { IconDefinition } from "@fortawesome/free-solid-svg-icons"; import { faAsterisk } from "@fortawesome/free-solid-svg-icons"; import { Chip, FontAwesomeIcon } from "@hashintel/design-system"; import { Box, Tooltip } from "@mui/material"; +import type { ReactNode } from "react"; export const ValueChip = ({ title, @@ -10,7 +11,7 @@ export const ValueChip = ({ selected, tooltip = "", }: { - title: string; + title: ReactNode; icon?: Pick; imageSrc?: string; selected: boolean; diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/editor-specs.ts b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/editor-specs.ts index 95b470e48be..4434478141c 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/editor-specs.ts +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/editor-specs.ts @@ -2,10 +2,8 @@ import type { ClosedDataType } from "@blockprotocol/type-system"; import type { IconDefinition } from "@fortawesome/free-solid-svg-icons"; import { fa100, - faAsterisk, faAtRegular, faBracketsCurly, - faBracketsSquare, faCalendarClockRegular, faCalendarRegular, faClockRegular, @@ -15,6 +13,7 @@ import { faSquareCheck, faText, } from "@hashintel/design-system"; +import type { MergedDataTypeSingleSchema } from "@local/hash-isomorphic-utils/data-types"; import type { CustomIcon } from "../../../../../../../../../components/grid/utils/custom-grid-icons"; import type { EditorType } from "./types"; @@ -49,13 +48,6 @@ const editorSpecs: Record = { arrayEditException: "no-save-and-discard-buttons", shouldBeDrawnAsAChip: true, }, - emptyList: { - icon: faBracketsSquare, - gridIcon: "bpBracketsSquare", - defaultValue: [], - arrayEditException: "no-edit-mode", - shouldBeDrawnAsAChip: true, - }, null: { icon: faEmptySet, gridIcon: "bpEmptySet", @@ -63,12 +55,6 @@ const editorSpecs: Record = { arrayEditException: "no-edit-mode", shouldBeDrawnAsAChip: true, }, - unknown: { - icon: faAsterisk, - gridIcon: "bpAsterisk", - defaultValue: "", - arrayEditException: "no-edit-mode", - }, }; const measurementTypeTitles = [ @@ -86,14 +72,14 @@ const measurementTypeTitles = [ const identifierTypeTitles = ["URL", "URI"]; export const getEditorSpecs = ( - editorType: EditorType, - dataType?: ClosedDataType, + dataType: ClosedDataType, + schema: MergedDataTypeSingleSchema, ): EditorSpec => { - switch (editorType) { + switch (schema.type) { case "boolean": return editorSpecs.boolean; case "number": - if (dataType?.title && measurementTypeTitles.includes(dataType.title)) { + if (dataType.title && measurementTypeTitles.includes(dataType.title)) { return { ...editorSpecs.number, icon: faRulerRegular, @@ -102,8 +88,8 @@ export const getEditorSpecs = ( } return editorSpecs.number; case "string": - if (dataType && "format" in dataType) { - switch (dataType.format) { + if ("format" in schema) { + switch (schema.format) { case "uri": return { ...editorSpecs.string, @@ -136,14 +122,14 @@ export const getEditorSpecs = ( }; } } - if (dataType?.title === "Email") { + if (dataType.title === "Email") { return { ...editorSpecs.string, icon: faAtRegular, gridIcon: "atRegular", }; } - if (dataType?.title && identifierTypeTitles.includes(dataType.title)) { + if (dataType.title && identifierTypeTitles.includes(dataType.title)) { return { ...editorSpecs.string, icon: faInputPipeRegular, @@ -153,13 +139,9 @@ export const getEditorSpecs = ( return editorSpecs.string; case "object": return editorSpecs.object; - case "emptyList": - return editorSpecs.emptyList; case "null": return editorSpecs.null; - case "unknown": - return editorSpecs.unknown; default: - throw new Error(`Unknown editor type: ${editorType}`); + throw new Error(`Unhandled type: ${schema.type}`); } }; diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/editor-type-picker.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/editor-type-picker.tsx index 69fe6f12d8e..f228d16d3eb 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/editor-type-picker.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/editor-type-picker.tsx @@ -1,10 +1,10 @@ import type { ClosedDataType } from "@blockprotocol/type-system/slim"; import { FontAwesomeIcon } from "@hashintel/design-system"; +import { getMergedDataTypeSchema } from "@local/hash-isomorphic-utils/data-types"; import { Box, ButtonBase, Typography } from "@mui/material"; import { getEditorSpecs } from "./editor-specs"; import type { OnTypeChange } from "./types"; -import { guessEditorTypeFromExpectedType } from "./utils"; const ExpectedTypeButton = ({ onClick, @@ -13,10 +13,15 @@ const ExpectedTypeButton = ({ onClick: () => void; expectedType: ClosedDataType; }) => { - const editorSpec = getEditorSpecs( - guessEditorTypeFromExpectedType(expectedType), - expectedType, - ); + const schema = getMergedDataTypeSchema(expectedType); + + if ("anyOf" in schema) { + throw new Error( + "Data types with different expected sets of constraints (anyOf) are not yet supported", + ); + } + + const editorSpec = getEditorSpecs(expectedType, schema); const { description, title } = expectedType; @@ -74,9 +79,7 @@ export const EditorTypePicker = ({ - onTypeChange(guessEditorTypeFromExpectedType(expectedType)) - } + onClick={() => onTypeChange(expectedType)} /> ); })} diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/inputs/number-or-text-input.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/inputs/number-or-text-input.tsx index 3deecef176c..d565f0d5484 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/inputs/number-or-text-input.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/inputs/number-or-text-input.tsx @@ -1,6 +1,6 @@ -import type { ValueConstraints } from "@blockprotocol/type-system-rs/pkg/type-system"; import type { TextFieldProps } from "@hashintel/design-system"; import { TextField } from "@hashintel/design-system"; +import type { MergedDataTypeSingleSchema } from "@local/hash-isomorphic-utils/data-types"; import { format, formatISO, parseISO } from "date-fns"; import type { CellInputProps } from "./types"; @@ -37,49 +37,46 @@ export const NumberOrTextInput = ({ onBlur, onChange, onEnterPressed, + schema, value: uncheckedValue, - valueConstraints, }: CellInputProps & { isNumber: boolean; onBlur?: TextFieldProps["onBlur"]; + schema: MergedDataTypeSingleSchema; onEnterPressed?: () => void; - valueConstraints: ValueConstraints; }) => { - const minLength = - "minLength" in valueConstraints ? valueConstraints.minLength : undefined; - const maxLength = - "maxLength" in valueConstraints ? valueConstraints.maxLength : undefined; + const minLength = "minLength" in schema ? schema.minLength : undefined; + const maxLength = "maxLength" in schema ? schema.maxLength : undefined; + + if ("multipleOf" in schema && schema.multipleOf?.[1] !== undefined) { + throw new Error("multipleOf with multiple values is not supported"); + } const step = - "multipleOf" in valueConstraints && - valueConstraints.multipleOf !== undefined - ? valueConstraints.multipleOf - : 0.01; + "multipleOf" in schema && schema.multipleOf?.[0] !== undefined + ? schema.multipleOf[0] + : 0.001; const exclusiveMinimum = - "exclusiveMinimum" in valueConstraints && - typeof valueConstraints.exclusiveMinimum === "boolean" - ? valueConstraints.exclusiveMinimum + "exclusiveMinimum" in schema && typeof schema.exclusiveMinimum === "boolean" + ? schema.exclusiveMinimum : false; + const minimum = - "minimum" in valueConstraints && - typeof valueConstraints.minimum === "number" - ? valueConstraints.minimum + (exclusiveMinimum ? step : 0) + "minimum" in schema && typeof schema.minimum === "number" + ? schema.minimum + (exclusiveMinimum ? step : 0) : undefined; const exclusiveMaximum = - "exclusiveMaximum" in valueConstraints && - typeof valueConstraints.exclusiveMaximum === "boolean" - ? valueConstraints.exclusiveMaximum + "exclusiveMaximum" in schema && typeof schema.exclusiveMaximum === "boolean" + ? schema.exclusiveMaximum : false; const maximum = - "maximum" in valueConstraints && - typeof valueConstraints.maximum === "number" - ? valueConstraints.maximum - (exclusiveMaximum ? step : 0) + "maximum" in schema && typeof schema.maximum === "number" + ? schema.maximum - (exclusiveMaximum ? step : 0) : undefined; - const jsonStringFormat = - "format" in valueConstraints ? valueConstraints.format : undefined; + const jsonStringFormat = "format" in schema ? schema.format : undefined; let inputType: TextFieldProps["type"] = isNumber ? "number" : "text"; let value = uncheckedValue; diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/single-value-editor.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/single-value-editor.tsx index b1c4d16af88..a72494cf3d0 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/single-value-editor.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/single-value-editor.tsx @@ -1,92 +1,168 @@ -import type { ValueConstraints } from "@blockprotocol/type-system-rs/pkg/type-system"; +import type { ClosedDataType } from "@blockprotocol/type-system"; import { Chip } from "@hashintel/design-system"; import { GRID_CLICK_IGNORE_CLASS } from "@hashintel/design-system/constants"; +import type { PropertyMetadataValue } from "@local/hash-graph-types/entity"; +import { isValueMetadata } from "@local/hash-graph-types/entity"; +import type { MergedDataTypeSingleSchema } from "@local/hash-isomorphic-utils/data-types"; +import { getMergedDataTypeSchema } from "@local/hash-isomorphic-utils/data-types"; import { Box } from "@mui/material"; import produce from "immer"; import { useEffect, useRef, useState } from "react"; import { GridEditorWrapper } from "../../../../shared/grid-editor-wrapper"; -import { isValueEmpty } from "../../../is-value-empty"; import { getEditorSpecs } from "./editor-specs"; import { EditorTypePicker } from "./editor-type-picker"; import { BooleanInput } from "./inputs/boolean-input"; import { JsonInput } from "./inputs/json-input"; import { NumberOrTextInput } from "./inputs/number-or-text-input"; -import type { EditorType, ValueCell, ValueCellEditorComponent } from "./types"; -import { - guessEditorTypeFromExpectedType, - guessEditorTypeFromValue, -} from "./utils"; +import type { ValueCell, ValueCellEditorComponent } from "./types"; export const SingleValueEditor: ValueCellEditorComponent = (props) => { const { value: cell, onChange, onFinishedEditing } = props; - const { permittedDataTypes, value } = cell.data.propertyRow; + const { + generateNewMetadataObject, + permittedDataTypes, + propertyKeyChain, + value, + valueMetadata, + } = cell.data.propertyRow; const textInputFormRef = useRef(null); - const [editorType, setEditorType] = useState(() => { - // if there are multiple expected types - if (permittedDataTypes.length > 1) { - // show type picker if value is empty, guess editor type using value if it's not - const guessedEditorType = guessEditorTypeFromValue( - value, - permittedDataTypes, - ); - - if (guessedEditorType === "null" || guessedEditorType === "emptyList") { - return guessedEditorType; + const [chosenDataType, setChosenDataType] = useState<{ + dataType: ClosedDataType; + schema: MergedDataTypeSingleSchema; + } | null>(() => { + if (permittedDataTypes.length === 1) { + const dataType = permittedDataTypes[0]!; + const schema = getMergedDataTypeSchema(dataType); + + if ("anyOf" in schema) { + throw new Error( + "Data types with different expected sets of constraints (anyOf) are not yet supported", + ); } - return isValueEmpty(value) - ? null - : guessEditorTypeFromValue(value, permittedDataTypes); + return { + dataType, + schema, + }; } - const expectedType = permittedDataTypes[0]; + if (!valueMetadata) { + /** + * We don't yet have a value set + */ + return null; + } - if (!expectedType) { - throw new Error("there is no expectedType found on property type"); + if (!isValueMetadata(valueMetadata)) { + throw new Error( + `Expected single value metadata in SingleValueEditor, got ${JSON.stringify(valueMetadata)}`, + ); } - // if the value is empty, guess the editor type from expected type - if (isValueEmpty(value)) { - return guessEditorTypeFromExpectedType(expectedType); + const dataTypeId = valueMetadata.metadata.dataTypeId; + + const dataType = permittedDataTypes.find((type) => type.$id === dataTypeId); + + if (!dataType) { + throw new Error( + "Expected a data type to be set on the value or at least one permitted data type", + ); } - // if the value is not empty, guess the editor type using value - return guessEditorTypeFromValue(value, permittedDataTypes); + const schema = getMergedDataTypeSchema(dataType); + + if ("anyOf" in schema) { + throw new Error( + "Data types with different expected sets of constraints (anyOf) are not yet supported", + ); + } + + return { + dataType, + schema, + }; }); + useEffect(() => { + if ( + chosenDataType && + (valueMetadata as PropertyMetadataValue | undefined)?.metadata + .dataTypeId !== chosenDataType.dataType.$id + ) { + const { propertyMetadata } = generateNewMetadataObject({ + propertyKeyChain, + valuePath: propertyKeyChain, + valueMetadata: { + metadata: { + dataTypeId: chosenDataType.dataType.$id, + }, + }, + }); + + const newCell = produce(cell, (draftCell) => { + draftCell.data.propertyRow.valueMetadata = propertyMetadata; + }); + + onChange(newCell); + } + }, [ + cell, + chosenDataType, + generateNewMetadataObject, + onChange, + propertyKeyChain, + valueMetadata, + ]); + const latestValueCellRef = useRef(cell); useEffect(() => { latestValueCellRef.current = cell; }); - if (!editorType) { + if (!chosenDataType || !cell.data.propertyRow.valueMetadata) { return ( { - const editorSpec = getEditorSpecs(type); + const schema = getMergedDataTypeSchema(type); + + if ("anyOf" in schema) { + throw new Error( + "Data types with different expected sets of constraints (anyOf) are not yet supported", + ); + } + + const editorSpec = getEditorSpecs(type, schema); + + setChosenDataType({ + dataType: type, + schema, + }); // if no edit mode supported for selected type, set the default value and close the editor if (editorSpec.arrayEditException === "no-edit-mode") { const newCell = produce(cell, (draftCell) => { draftCell.data.propertyRow.value = editorSpec.defaultValue; + draftCell.data.propertyRow.valueMetadata = { + metadata: { dataTypeId: type.$id }, + }; }); return onFinishedEditing(newCell); } - - setEditorType(type); }} /> ); } - if (editorType === "boolean") { + const { schema } = chosenDataType; + + if (schema.type === "boolean") { return ( { ); } - if (editorType === "object") { + if (schema.type === "object") { return ( { ); } - if (editorType === "null" || editorType === "emptyList") { - const spec = getEditorSpecs(editorType); - const title = editorType === "null" ? "Null" : "Empty List"; + if (schema.type === "null") { + const spec = getEditorSpecs(chosenDataType.dataType, chosenDataType.schema); + const title = "Null"; const shouldClearOnClick = value !== undefined; @@ -148,38 +224,6 @@ export const SingleValueEditor: ValueCellEditorComponent = (props) => { ); } - let valueConstraints: ValueConstraints | undefined; - /** @todo H-3374 don't guess the type, take it from the data type metadata */ - /* eslint-disable no-labels */ - outerLoop: for (const expectedType of permittedDataTypes) { - for (const constraint of expectedType.allOf) { - if ("type" in constraint) { - if (constraint.type === editorType) { - valueConstraints = constraint; - break outerLoop; - } - } else { - for (const innerConstraint of constraint.anyOf) { - if ("type" in innerConstraint) { - if (innerConstraint.type === editorType) { - valueConstraints = innerConstraint; - break outerLoop; - } - } - } - } - } - } - /* eslint-enable no-labels */ - - if (!valueConstraints) { - throw new Error( - `Could not find guessed editor type ${editorType} among expected types ${permittedDataTypes - .map((opt) => opt.$id) - .join(", ")}`, - ); - } - /** * Force validation on the text input form. * If the form is valid or if the form has been unmounted, allow to handle click events again. @@ -198,10 +242,10 @@ export const SingleValueEditor: ValueCellEditorComponent = (props) => { * 1. The form is valid * 2. The form has been unmounted and we can't check its validity – this happens if another grid cell is clicked * - * If another grid cell is clicked, we cannot validate using the input and we may have an invalid value in the table. - * The alternative is that clicking another cell wipes the value, which is slightly worse UX. - * Ideally we would prevent the form being closed when another cell is clicked, to allow validation to run, - * and in any case have an indicator that an invalid value is in the form – tracked in H-1834 + * If another grid cell is clicked, we cannot validate using the input and we may have an invalid value in the + * table. The alternative is that clicking another cell wipes the value, which is slightly worse UX. Ideally we + * would prevent the form being closed when another cell is clicked, to allow validation to run, and in any case + * have an indicator that an invalid value is in the form – tracked in H-1834 */ onFinishedEditing(latestValueCellRef.current); document.removeEventListener("click", validationHandler); @@ -234,34 +278,32 @@ export const SingleValueEditor: ValueCellEditorComponent = (props) => { ref={textInputFormRef} > { if ( - ("format" in valueConstraints && - valueConstraints.format && + ("format" in schema && + schema.format && /** * We use the native browser date/time inputs which handle validation for us, * and the validation click handler assumes there will be a click outside after a change * - which there won't for those inputs, because clicking to select a value closes the input. */ - !["date", "date-time", "time"].includes( - valueConstraints.format, - )) || - "minLength" in valueConstraints || - "maxLength" in valueConstraints || - "minimum" in valueConstraints || - "maximum" in valueConstraints || - "step" in valueConstraints + !["date", "date-time", "time"].includes(schema.format)) || + "minLength" in schema || + "maxLength" in schema || + "minimum" in schema || + "maximum" in schema || + "step" in schema ) { /** * Add the validation enforcer if there are any validation rules. - * We don't add this if we know the user cannot input an invalid value (e.g. unconstrained string or number). - * Adding the validation enforcer means clicking into another cell requires a second click to activate it, - * so we don't want to add it unnecessarily. - * Ideally we wouldn't need the click handler hacks to enforce validation, or have a different validation strategy – - * especially given that the validation enforcement can be bypassed by clicking another cell – see H-1834. + * We don't add this if we know the user cannot input an invalid value (e.g. unconstrained string or + * number). Adding the validation enforcer means clicking into another cell requires a second click to + * activate it, so we don't want to add it unnecessarily. Ideally we wouldn't need the click handler + * hacks to enforce validation, or have a different validation strategy – especially given that the + * validation enforcement can be bypassed by clicking another cell – see H-1834. */ ensureFormValidation(); } diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/types.ts b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/types.ts index 59d8ba2f0c0..8e5f47a7c92 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/types.ts +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/types.ts @@ -1,3 +1,4 @@ +import type { ClosedDataType } from "@blockprotocol/type-system"; import type { CustomCell, ProvideEditorComponent, @@ -14,15 +15,8 @@ export interface ValueCellProps extends TooltipCellProps { export type ValueCell = CustomCell; -export type EditorType = - | "boolean" - | "number" - | "string" - | "object" - | "emptyList" - | "null" - | "unknown"; +export type EditorType = "boolean" | "null" | "number" | "object" | "string"; -export type OnTypeChange = (type: EditorType) => void; +export type OnTypeChange = (dataType: ClosedDataType) => void; export type ValueCellEditorComponent = ProvideEditorComponent; diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/utils.ts b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/utils.ts index 910680f5a5b..7a2b6d81a34 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/utils.ts +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/cells/value-cell/utils.ts @@ -1,98 +1,3 @@ -import type { ClosedDataType } from "@blockprotocol/type-system/slim"; -import isPlainObject from "lodash/isPlainObject"; - -import type { EditorType } from "./types"; - -const isEmptyArray = (value: unknown) => Array.isArray(value) && !value.length; - -const isValidTypeForSchemas = ( - type: "string" | "boolean" | "number" | "object" | "null", - expectedTypes: ClosedDataType[], -) => - expectedTypes.some(({ allOf }) => - allOf.some((constraint) => - "type" in constraint - ? constraint.type === type - : constraint.anyOf.some((subType) => subType.type === type), - ), - ); - -/** - * @todo H-3374 we don't need to guess the type anymore, because the exact dataTypeId will be in the entity's metadata - */ -export const guessEditorTypeFromValue = ( - value: unknown, - expectedTypes: ClosedDataType[], -): EditorType => { - if ( - typeof value === "string" && - isValidTypeForSchemas("string", expectedTypes) - ) { - return "string"; - } - - if ( - typeof value === "boolean" && - isValidTypeForSchemas("boolean", expectedTypes) - ) { - return "boolean"; - } - - if ( - typeof value === "number" && - isValidTypeForSchemas("number", expectedTypes) - ) { - return "number"; - } - - if (isPlainObject(value) && isValidTypeForSchemas("object", expectedTypes)) { - return "object"; - } - - if (value === null && isValidTypeForSchemas("null", expectedTypes)) { - return "null"; - } - - if ( - isEmptyArray(value) && - expectedTypes.some((dataType) => dataType.title === "Empty List") - ) { - return "emptyList"; - } - - return "unknown"; -}; - -export const guessEditorTypeFromExpectedType = ( - dataType: ClosedDataType, -): EditorType => { - if (dataType.title === "Empty List") { - return "emptyList"; - } - - let type: "string" | "number" | "boolean" | "object" | "null" | "array"; - - const firstConstraint = dataType.allOf[0]; - - if ("anyOf" in firstConstraint) { - /** - * @todo H-3374 support multiple expected data types - */ - type = firstConstraint.anyOf[0].type; - } else { - type = firstConstraint.type; - } - - if (type === "array") { - /** - * @todo H-3374 support array and tuple data types - */ - throw new Error("Array data types are not yet handled."); - } - - return type; -}; - export const isBlankStringOrNullish = (value: unknown) => { const isBlankString = typeof value === "string" && !value.trim().length; return isBlankString || value === null || value === undefined; diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/types.ts b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/types.ts index 005408063e2..c6d6453dc60 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/types.ts +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/types.ts @@ -1,24 +1,47 @@ import type { ClosedDataType } from "@blockprotocol/type-system"; import type { SizedGridColumn } from "@glideapps/glide-data-grid"; -import type { PropertyMetadata } from "@local/hash-graph-types/entity"; +import type { + PropertyMetadata, + PropertyMetadataObject, + PropertyMetadataValue, + PropertyPath, +} from "@local/hash-graph-types/entity"; import type { VerticalIndentationLineDir } from "../../../../../../../components/grid/utils/draw-vertical-indentation-line"; export type PropertyRow = { children: PropertyRow[]; depth: number; + generateNewMetadataObject: (args: { + /** + * The path to the property in the entity's properties (i.e. row.propertyKeyChain) + */ + propertyKeyChain: PropertyPath; + /** + * The path to the leaf value in the entity's properties, + * which will start with propertyKeyChain, but may have additional array indices (depending on the property's structure) + */ + valuePath: PropertyPath; + /** + * The metadata to set for the leaf value + */ + valueMetadata: PropertyMetadataValue | "delete"; + }) => { + entityPropertiesMetadata: PropertyMetadataObject; + propertyMetadata: PropertyMetadata | undefined; + }; indent: number; isArray: boolean; isSingleUrl: boolean; maxItems?: number; minItems?: number; permittedDataTypes: ClosedDataType[]; - propertyKeyChain: string[]; + propertyKeyChain: PropertyPath; required: boolean; rowId: string; title: string; value: unknown; - valueMetadata: PropertyMetadata["metadata"]; + valueMetadata?: PropertyMetadata; verticalLinesForEachIndent: VerticalIndentationLineDir[]; }; diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/use-create-get-cell-content.ts b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/use-create-get-cell-content.ts index 159a3b1f1ca..546c4621887 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/use-create-get-cell-content.ts +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/use-create-get-cell-content.ts @@ -1,5 +1,7 @@ import type { Item } from "@glideapps/glide-data-grid"; import { GridCellKind } from "@glideapps/glide-data-grid"; +import { isValueMetadata } from "@local/hash-graph-types/entity"; +import { getMergedDataTypeSchema } from "@local/hash-isomorphic-utils/data-types"; import { useCallback } from "react"; import type { BlankCell } from "../../../../../../../components/grid/utils"; @@ -9,15 +11,10 @@ import type { ChipCell } from "../../../../../../shared/chip-cell"; import { useEntityEditor } from "../../entity-editor-context"; import type { SummaryChipCell } from "../../shared/summary-chip-cell"; import { getPropertyCountSummary } from "../get-property-count-summary"; -import { isValueEmpty } from "../is-value-empty"; import type { ChangeTypeCell } from "./cells/change-type-cell"; import type { PropertyNameCell } from "./cells/property-name-cell"; import { getEditorSpecs } from "./cells/value-cell/editor-specs"; import type { ValueCell } from "./cells/value-cell/types"; -import { - guessEditorTypeFromExpectedType, - guessEditorTypeFromValue, -} from "./cells/value-cell/utils"; import { propertyGridIndexes } from "./constants"; import { getTooltipsOfPropertyRow } from "./get-tooltips-of-property-row"; import type { PropertyRow } from "./types"; @@ -67,20 +64,12 @@ export const useCreateGetCellContent = ( }, }; - const guessedType = guessEditorTypeFromValue( - row.value, - row.permittedDataTypes, - ); - - const isEmptyValue = - isValueEmpty(row.value) && - guessedType !== "null" && - guessedType !== "emptyList"; + const { isArray, permittedDataTypes, value, valueMetadata } = row; const shouldShowChangeTypeCell = - row.permittedDataTypes.length > 1 && - !row.isArray && - !isEmptyValue && + permittedDataTypes.length > 1 && + !isArray && + typeof value !== "undefined" && !readonly; switch (columnKey) { @@ -125,21 +114,27 @@ export const useCreateGetCellContent = ( } if (shouldShowChangeTypeCell) { - const currentType = row.permittedDataTypes.find(({ allOf }) => - allOf.some((constraint) => - "type" in constraint - ? constraint.type === guessedType - : /** - * @todo H-3374 support anyOf in expected types. also don't need to guess the value any more, use dataTypeId from property metadata - */ - constraint.anyOf.some( - (subType) => subType.type === guessedType, - ), - ), + if (!valueMetadata) { + throw new Error( + `Expected value metadata to be set when showing change type cell`, + ); + } + + if (!isValueMetadata(valueMetadata)) { + throw new Error( + `Expected single value when showing change type cell`, + ); + } + + const dataTypeId = valueMetadata.metadata.dataTypeId; + + const dataType = permittedDataTypes.find( + (type) => type.$id === dataTypeId, ); - if (!currentType) { + + if (!dataType) { throw new Error( - `dataType for guessed type ${guessedType} not found`, + "Expected a data type to be set on the value or at least one permitted data type", ); } @@ -147,11 +142,11 @@ export const useCreateGetCellContent = ( kind: GridCellKind.Custom, allowOverlay: false, readonly: true, - copyData: guessedType, + copyData: dataType.$id, cursor: "pointer", data: { kind: "change-type-cell", - currentType, + currentType: dataType, propertyRow: row, valueCellOfThisRow: valueCell, }, @@ -166,10 +161,15 @@ export const useCreateGetCellContent = ( data: { kind: "chip-cell", chips: row.permittedDataTypes.map((type) => { - const editorSpec = getEditorSpecs( - guessEditorTypeFromExpectedType(type), - type, - ); + const schema = getMergedDataTypeSchema(type); + + if ("anyOf" in schema) { + throw new Error( + "Data types with different expected sets of constraints (anyOf) are not yet supported", + ); + } + + const editorSpec = getEditorSpecs(type, schema); return { text: type.title, diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/use-create-on-cell-edited.ts b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/use-create-on-cell-edited.ts index 03f91b51169..06b671ffc37 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/use-create-on-cell-edited.ts +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/use-create-on-cell-edited.ts @@ -1,7 +1,6 @@ import type { EditableGridCell, Item } from "@glideapps/glide-data-grid"; import { GridCellKind } from "@glideapps/glide-data-grid"; import { Entity } from "@local/hash-graph-sdk/entity"; -import { getRoots } from "@local/hash-subgraph/stdlib"; import cloneDeep from "lodash/cloneDeep"; import set from "lodash/set"; import { useCallback } from "react"; @@ -15,7 +14,7 @@ import type { PropertyRow } from "./types"; * This onCellEdited is used to handle editing the data only at `Values` column */ export const useCreateOnCellEdited = () => { - const { entitySubgraph, setEntity } = useEntityEditor(); + const { entity, setEntity } = useEntityEditor(); const createOnCellEdited = useCallback( (rows: PropertyRow[]) => { @@ -27,9 +26,7 @@ export const useCreateOnCellEdited = () => { return; } - const entity = getRoots(entitySubgraph)[0]!; - - const valueCell = newValue as ValueCell; + const newValueCell = newValue as ValueCell; const key = propertyGridIndexes[colIndex]; const row = rows[rowIndex]; @@ -45,6 +42,8 @@ export const useCreateOnCellEdited = () => { const updatedProperties = cloneDeep(entity.properties); + const updatedMetadata = cloneDeep(entity.metadata); + const { propertyKeyChain } = row; /** @@ -55,12 +54,19 @@ export const useCreateOnCellEdited = () => { set( updatedProperties, propertyKeyChain, - valueCell.data.propertyRow.value, + newValueCell.data.propertyRow.value, + ); + + set( + updatedMetadata, + ["properties", "value", ...propertyKeyChain], + newValueCell.data.propertyRow.valueMetadata, ); setEntity( new Entity({ ...entity.toJSON(), + metadata: updatedMetadata, properties: updatedProperties, }), ); @@ -68,7 +74,7 @@ export const useCreateOnCellEdited = () => { return onCellEdited; }, - [entitySubgraph, setEntity], + [entity, setEntity], ); return createOnCellEdited; diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/use-rows/generate-property-rows-from-entity/generate-property-row-recursively.ts b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/use-rows/generate-property-rows-from-entity/generate-property-row-recursively.ts index 466d304bf04..442018aaeb0 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/use-rows/generate-property-rows-from-entity/generate-property-row-recursively.ts +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/use-rows/generate-property-rows-from-entity/generate-property-row-recursively.ts @@ -53,6 +53,7 @@ import { getExpectedTypesOfPropertyType } from "./get-expected-types-of-property export const generatePropertyRowRecursively = ({ closedMultiEntityType, closedMultiEntityTypesDefinitions, + generateNewMetadataObject, propertyTypeBaseUrl, propertyKeyChain, entity, @@ -62,6 +63,7 @@ export const generatePropertyRowRecursively = ({ }: { closedMultiEntityType: ClosedMultiEntityType; closedMultiEntityTypesDefinitions: ClosedMultiEntityTypesDefinitions; + generateNewMetadataObject: PropertyRow["generateNewMetadataObject"]; propertyTypeBaseUrl: BaseUrl; propertyKeyChain: BaseUrl[]; entity: Entity; @@ -81,12 +83,22 @@ export const generatePropertyRowRecursively = ({ throw new Error(`Property type ${propertyTypeId} not found in definitions`); } - const { isArray: isPropertyTypeArray, expectedTypes } = - getExpectedTypesOfPropertyType( - propertyType, - closedMultiEntityTypesDefinitions, - ); - + const { + /** + * Whether the property type specifies that it expects an array of values. + */ + isArray: isPropertyTypeArray, + expectedTypes, + } = getExpectedTypesOfPropertyType( + propertyType, + closedMultiEntityTypesDefinitions, + ); + + /** + * Whether the entity type has specified that it expects multiple instances of whatever value this property expects. + * Note that the editor currently only supports 1D arrays, and therefore does not support + * isPropertyTypeArray && isAllowMultiple, which would be a 2D array. + */ const isAllowMultiple = "type" in propertyRefSchema; const isArray = isPropertyTypeArray || isAllowMultiple; @@ -96,6 +108,10 @@ export const generatePropertyRowRecursively = ({ const value = get(entity.properties, propertyKeyChain); const valueMetadata = entity.propertyMetadata(propertyKeyChain); + if (value !== undefined && !valueMetadata) { + throw new Error(`Property metadata not found for path ${propertyKeyChain}`); + } + const children: PropertyRow[] = []; const firstOneOf = propertyType.oneOf[0]; @@ -111,6 +127,7 @@ export const generatePropertyRowRecursively = ({ generatePropertyRowRecursively({ closedMultiEntityType, closedMultiEntityTypesDefinitions, + generateNewMetadataObject, propertyTypeBaseUrl: subPropertyTypeBaseUrl as BaseUrl, propertyKeyChain: [ ...propertyKeyChain, @@ -164,6 +181,7 @@ export const generatePropertyRowRecursively = ({ ...minMaxConfig, children, depth, + generateNewMetadataObject, indent, isArray, isSingleUrl, diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/use-rows/use-property-rows-from-entity.ts b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/use-rows/use-property-rows-from-entity.ts index 16892f0179a..9b83d9e5994 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/use-rows/use-property-rows-from-entity.ts +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/use-rows/use-property-rows-from-entity.ts @@ -1,6 +1,14 @@ import { typedKeys } from "@local/advanced-types/typed-entries"; +import { + Entity, + generateChangedPropertyMetadataObject, +} from "@local/hash-graph-sdk/entity"; +import type { + PropertyMetadataObject, + PropertyMetadataValue, + PropertyPath, +} from "@local/hash-graph-types/entity"; import type { BaseUrl } from "@local/hash-graph-types/ontology"; -import { getRoots } from "@local/hash-subgraph/stdlib"; import { useMemo } from "react"; import { useEntityEditor } from "../../../entity-editor-context"; @@ -8,15 +16,59 @@ import type { PropertyRow } from "../types"; import { generatePropertyRowRecursively } from "./generate-property-rows-from-entity/generate-property-row-recursively"; export const usePropertyRowsFromEntity = (): PropertyRow[] => { - const { - entitySubgraph, - closedMultiEntityType, - closedMultiEntityTypesDefinitions, - } = useEntityEditor(); + const { entity, closedMultiEntityType, closedMultiEntityTypesDefinitions } = + useEntityEditor(); - return useMemo(() => { - const entity = getRoots(entitySubgraph)[0]!; + /** + * Generate a new metadata object based on applying a patch to the previous version. + * + * We can't use the Entity's metadata as a base each time because the ArrayEditor allows adding multiple items + * before the editor is closed and before the Entity in state is updated. So we need to keep a record of changes to the metadata object, + * which are reset when the entity in the context state is actually updated (after the editor is closed). + */ + const generateNewMetadataObject = useMemo< + PropertyRow["generateNewMetadataObject"] + >(() => { + let basePropertiesMetadata = JSON.parse( + JSON.stringify( + entity.metadata.properties ?? + ({ value: {} } satisfies PropertyMetadataObject), + ), + ); + + return ({ + propertyKeyChain, + valuePath, + valueMetadata, + }: { + propertyKeyChain: PropertyPath; + valuePath: PropertyPath; + valueMetadata: PropertyMetadataValue | "delete"; + }) => { + basePropertiesMetadata = generateChangedPropertyMetadataObject( + valuePath, + valueMetadata, + basePropertiesMetadata, + ); + const temporaryEntity = new Entity({ + ...entity.toJSON(), + metadata: { + ...entity.metadata, + properties: basePropertiesMetadata, + }, + }); + + const pathMetadata = temporaryEntity.propertyMetadata(propertyKeyChain); + + return { + propertyMetadata: pathMetadata, + entityPropertiesMetadata: basePropertiesMetadata, + }; + }; + }, [entity]); + + return useMemo(() => { const processedPropertyTypes = new Set(); return typedKeys(closedMultiEntityType.properties).flatMap( @@ -37,6 +89,7 @@ export const usePropertyRowsFromEntity = (): PropertyRow[] => { return generatePropertyRowRecursively({ closedMultiEntityType, closedMultiEntityTypesDefinitions, + generateNewMetadataObject, propertyTypeBaseUrl: propertyTypeBaseUrl as BaseUrl, propertyKeyChain: [propertyTypeBaseUrl as BaseUrl], entity, @@ -47,8 +100,9 @@ export const usePropertyRowsFromEntity = (): PropertyRow[] => { }, ); }, [ - entitySubgraph, + entity, closedMultiEntityType, closedMultiEntityTypesDefinitions, + generateNewMetadataObject, ]); }; diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/types-section.tsx b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/types-section.tsx index 8492396c71d..435fd5ec359 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/types-section.tsx +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/types-section.tsx @@ -182,11 +182,9 @@ export const TypeButton = ({ }; export const TypesSection = () => { - const { entitySubgraph, closedMultiEntityType, readonly, handleTypesChange } = + const { entity, closedMultiEntityType, readonly, handleTypesChange } = useEntityEditor(); - const entity = getRoots(entitySubgraph)[0]!; - const { data: latestEntityTypesData } = useQuery< QueryEntityTypesQuery, QueryEntityTypesQueryVariables diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/types-section/use-get-type-change-details.ts b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/types-section/use-get-type-change-details.ts index 221c18a1ffd..24571176756 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/types-section/use-get-type-change-details.ts +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/types-section/use-get-type-change-details.ts @@ -2,7 +2,6 @@ import type { VersionedUrl } from "@blockprotocol/type-system/slim"; import { extractVersion } from "@blockprotocol/type-system/slim"; import { typedEntries, typedKeys } from "@local/advanced-types/typed-entries"; import { getPropertyTypeForClosedEntityType } from "@local/hash-graph-sdk/entity"; -import { getRoots } from "@local/hash-subgraph/stdlib"; import { componentsFromVersionedUrl, extractBaseUrl, @@ -19,7 +18,6 @@ export const useGetTypeChangeDetails = () => { const { closedMultiEntityType: currentClosedType, closedMultiEntityTypesDefinitions: currentDefinitions, - entitySubgraph, } = useEntityEditor(); return useCallback( @@ -28,12 +26,6 @@ export const useGetTypeChangeDetails = () => { ): Promise< Pick > => { - const entity = getRoots(entitySubgraph)[0]; - - if (!entity) { - throw new Error("No entity found in entitySubgraph"); - } - const { closedMultiEntityType: proposedClosedMultiType, closedMultiEntityTypesDefinitions: proposedDefinitions, @@ -296,11 +288,6 @@ export const useGetTypeChangeDetails = () => { return changeDetails; }, - [ - currentClosedType, - currentDefinitions, - entitySubgraph, - getClosedMultiEntityType, - ], + [currentClosedType, currentDefinitions, getClosedMultiEntityType], ); }; diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/shared/create-draft-entity-subgraph.ts b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/shared/create-draft-entity-subgraph.ts index 59296eb0b9e..79aa91a4a65 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/shared/create-draft-entity-subgraph.ts +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/shared/create-draft-entity-subgraph.ts @@ -44,11 +44,13 @@ export const createDraftEntitySubgraph = ({ ), ); - const newEntity = new Entity({ - ...entity.toJSON(), - metadata, - properties: newProperties, - }); + const newEntity = Object.freeze( + new Entity({ + ...entity.toJSON(), + metadata, + properties: newProperties, + }), + ); return { depths: { diff --git a/apps/hash-frontend/src/pages/shared/data-types-context.tsx b/apps/hash-frontend/src/pages/shared/data-types-context.tsx index e144c0eda9a..0990daae794 100644 --- a/apps/hash-frontend/src/pages/shared/data-types-context.tsx +++ b/apps/hash-frontend/src/pages/shared/data-types-context.tsx @@ -1,14 +1,9 @@ import { useQuery } from "@apollo/client"; -import type { JsonValue } from "@blockprotocol/core"; import type { VersionedUrl } from "@blockprotocol/type-system"; import { typedValues } from "@local/advanced-types/typed-entries"; import type { DataTypeWithMetadata } from "@local/hash-graph-types/ontology"; -import type { FormattedValuePart } from "@local/hash-isomorphic-utils/data-types"; -import { formatDataValue } from "@local/hash-isomorphic-utils/data-types"; -import type { OntologyTypeRevisionId } from "@local/hash-subgraph"; -import { componentsFromVersionedUrl } from "@local/hash-subgraph/type-system-patch"; import type { PropsWithChildren } from "react"; -import { createContext, useCallback, useContext, useMemo } from "react"; +import { createContext, useContext, useMemo } from "react"; import type { QueryDataTypesQuery, @@ -18,10 +13,6 @@ import { queryDataTypesQuery } from "../../graphql/queries/ontology/data-type.qu export type DataTypesContextValue = { dataTypes: Record | null; - formatDataTypeValue: (args: { - dataTypeId: VersionedUrl; - value: JsonValue; - }) => FormattedValuePart[] | null; }; export const DataTypesContext = createContext( @@ -55,29 +46,11 @@ export const DataTypesContextProvider = ({ children }: PropsWithChildren) => { return allDataTypes; }, [data]); - const formatDataTypeValue = useCallback( - ({ dataTypeId, value }: { dataTypeId: VersionedUrl; value: JsonValue }) => { - const { baseUrl, version } = componentsFromVersionedUrl(dataTypeId); - - const dataType = data?.queryDataTypes.vertices[baseUrl]?.[ - version as unknown as OntologyTypeRevisionId - ]?.inner.schema as DataTypeWithMetadata["schema"] | undefined; - - if (!dataType) { - return formatDataValue(value?.toString() ?? "null", undefined); - } - - return formatDataValue(value, dataType); - }, - [data], - ); - const value = useMemo(() => { return { dataTypes, - formatDataTypeValue, }; - }, [dataTypes, formatDataTypeValue]); + }, [dataTypes]); return ( diff --git a/apps/hash-frontend/src/pages/shared/entity-selector.tsx b/apps/hash-frontend/src/pages/shared/entity-selector.tsx index 6acd8257ad6..8584f8da358 100644 --- a/apps/hash-frontend/src/pages/shared/entity-selector.tsx +++ b/apps/hash-frontend/src/pages/shared/entity-selector.tsx @@ -78,7 +78,7 @@ export const EntitySelector = ({ : { any: expectedEntityTypes.map(({ $id }) => generateVersionedUrlMatchingFilter($id, { - ignoreParents: true, + ignoreParents: false, }), ), }, @@ -89,6 +89,7 @@ export const EntitySelector = ({ }, includePermissions: false, }, + fetchPolicy: "cache-and-network", }); const entitiesSubgraph = entitiesData @@ -222,7 +223,7 @@ export const EntitySelector = ({ }; }), ), - title: generateEntityLabel(entitiesSubgraph!, entity), + title: generateEntityLabel(closedType, entity), draft: !!extractDraftIdFromEntityId( entity.metadata.recordId.entityId, ), @@ -230,14 +231,30 @@ export const EntitySelector = ({ }} inputPlaceholder="Search for an entity" renderTags={(tagValue, getTagProps) => - tagValue.map((option, index) => ( - - )) + tagValue.map((option, index) => { + const typesMap = + entitiesData?.getEntitySubgraph.closedMultiEntityTypes; + + if (!typesMap) { + throw new Error( + "Cannot render an entity without a closed multi entity types map", + ); + } + + const closedType = getClosedMultiEntityTypeFromMap( + typesMap, + option.metadata.entityTypeIds, + ); + + return ( + + ); + }) } {...autocompleteProps} dropdownProps={{ diff --git a/apps/hash-frontend/src/pages/shared/use-validate-entity.ts b/apps/hash-frontend/src/pages/shared/use-validate-entity.ts new file mode 100644 index 00000000000..df003735313 --- /dev/null +++ b/apps/hash-frontend/src/pages/shared/use-validate-entity.ts @@ -0,0 +1,124 @@ +import { useLazyQuery } from "@apollo/client"; +import type { VersionedUrl } from "@blockprotocol/type-system"; +import type { PropertyObjectWithMetadata } from "@local/hash-graph-types/entity"; +import { stringifyError } from "@local/hash-isomorphic-utils/stringify-error"; +import { useCallback } from "react"; + +import type { + ValidateEntityQuery, + ValidateEntityQueryVariables, +} from "../../graphql/api-types.gen"; +import { validateEntityQuery } from "../../graphql/queries/knowledge/entity.queries"; + +type ContextObject = { + context: string; + sources: ContextObject[]; +}; + +const isContextObject = (obj: unknown): obj is ContextObject => + !!obj && + typeof obj === "object" && + "context" in obj && + typeof obj.context === "string" && + "sources" in obj && + Array.isArray(obj.sources); + +/** + * Extract validation failure messages from the error object, which at the time of writing looks like this: + * + * { + * "status": { + * "code": "INVALID_ARGUMENT", + * "message": "Entity validation failed", + * "contents": [ + * [ + * { + * "context": "Entity validation failed", + * "attachments": [], + * "sources": [ + * { + * "context": "The properties of the entity do not match the schema", + * "attachments": [], + * "sources": [ // This might be empty depending on where the validation failure occurs + * { + * "context": "the property `https://hash.ai/@hash/types/property-type/title/` was specified, but not in the schema", + * "attachments": [], + * "sources": [] + * }, + */ +export const extractEntityValidationErrors = (err: unknown) => { + try { + if ( + err && + typeof err === "object" && + "status" in err && + err.status && + typeof err.status === "object" && + "contents" in err.status && + Array.isArray(err.status.contents) && + Array.isArray(err.status.contents[0]) && + isContextObject(err.status.contents[0][0]) + ) { + const nestedContextObject = err.status.contents[0][0].sources[0]; + if (nestedContextObject) { + const thisLevelContextMessage = nestedContextObject.context; + + const nestedAgainFailureMessages = nestedContextObject.sources.map( + (source) => source.context, + ); + + return nestedAgainFailureMessages.length + ? nestedAgainFailureMessages + : thisLevelContextMessage; + } + } + } catch { + // eslint-disable-next-line no-console + console.error(`Unexpected error message structure: ${stringifyError(err)}`); + } + return stringifyError(err); +}; + +export const useValidateEntity = () => { + const [validate, { loading }] = useLazyQuery< + ValidateEntityQuery, + ValidateEntityQueryVariables + >(validateEntityQuery, { + fetchPolicy: "network-only", + }); + + const validateEntity = useCallback( + async ({ + entityTypeIds, + properties, + }: { + entityTypeIds: VersionedUrl[]; + properties: PropertyObjectWithMetadata; + }) => { + try { + await validate({ + variables: { + components: { + linkData: true, + linkValidation: true, + numItems: true, + requiredProperties: true, + }, + entityTypes: entityTypeIds, + properties, + }, + }); + + return true; + } catch (err) { + return extractEntityValidationErrors(err); + } + }, + [validate], + ); + + return { + loading, + validateEntity, + }; +}; diff --git a/apps/hash-frontend/src/shared/file-upload-context.tsx b/apps/hash-frontend/src/shared/file-upload-context.tsx index 1306fa8327e..db36ccbb683 100644 --- a/apps/hash-frontend/src/shared/file-upload-context.tsx +++ b/apps/hash-frontend/src/shared/file-upload-context.tsx @@ -236,7 +236,7 @@ export const FileUploadsProvider = ({ children }: PropsWithChildren) => { const newRequestId = requestId ? undefined : uuid(); - const upload = + let upload: FileUpload = existingUpload ?? ({ fileData, @@ -359,13 +359,13 @@ export const FileUploadsProvider = ({ children }: PropsWithChildren) => { presignedPut = data.requestFileUpload.presignedPut; - const updatedUpload: FileUpload = { + upload = { ...upload, createdEntities: { fileEntity }, presignedPut, status: "uploading-file-locally", }; - updateUpload(updatedUpload); + updateUpload(upload); } if (!presignedPut) { @@ -423,6 +423,7 @@ export const FileUploadsProvider = ({ children }: PropsWithChildren) => { const updatedUpload: FileUpload = { ...upload, ...(fileEntity ? { createdEntities: { fileEntity } } : {}), + presignedPut, status: "error", failedStep: "uploading-file-locally", errorMessage, @@ -463,13 +464,14 @@ export const FileUploadsProvider = ({ children }: PropsWithChildren) => { linkEntityIdToDelete && existingUpload?.failedStep !== "creating-link-entity" ) { - updateUpload({ + upload = { ...upload, createdEntities: { fileEntity, }, status: "archiving-link-entity", - }); + }; + updateUpload(upload); try { const { data: archiveData, errors: archiveErrors } = @@ -500,13 +502,15 @@ export const FileUploadsProvider = ({ children }: PropsWithChildren) => { } } - updateUpload({ + upload = { ...upload, createdEntities: { fileEntity, }, status: "creating-link-entity", - }); + }; + + updateUpload(upload); try { const { data, errors } = await createEntity({ diff --git a/libs/@hashintel/design-system/src/selector-autocomplete/selector-autocomplete-option.tsx b/libs/@hashintel/design-system/src/selector-autocomplete/selector-autocomplete-option.tsx index 53f1ff9c5dd..4035ab70491 100644 --- a/libs/@hashintel/design-system/src/selector-autocomplete/selector-autocomplete-option.tsx +++ b/libs/@hashintel/design-system/src/selector-autocomplete/selector-autocomplete-option.tsx @@ -100,7 +100,17 @@ export const SelectorAutocompleteOption = ({ src={imageUrl} sx={{ mb: 1 }} /> - {title} + + {title} + {subtitle && ( {subtitle} @@ -144,7 +154,7 @@ export const SelectorAutocompleteOption = ({ component="span" display="flex" alignItems="center" - maxWidth="50%" + maxWidth="60%" > type.allOf ?? []) @@ -508,7 +511,8 @@ export const getDisplayFieldsForClosedEntityType = ( isLink = $id === /** - * Ideally this wouldn't be hardcoded but the only places to import it from would create a circular dependency between packages + * Ideally this wouldn't be hardcoded but the only places to import it from would create a circular dependency + * between packages * @todo do something about this */ "https://blockprotocol.org/@blockprotocol/types/entity-type/link/v/1"; @@ -679,6 +683,217 @@ export const getPropertyTypeForClosedEntityType = ({ }; }; +/** + * Generate a new property metadata object based on an existing one, with a value's metadata set or deleted. + * This is a temporary solution to be replaced by the SDK accepting {@link PropertyPatchOperation}s directly, + * which it then applies to the entity to generate the new properties and metadata. + * + * @returns {PropertyMetadataObject} a new object with the changed metadata + * @throws {Error} if the path is not supported (complex arrays or property objects) + */ +export const generateChangedPropertyMetadataObject = ( + path: PropertyPath, + metadata: PropertyMetadataValue | "delete", + baseMetadataObject: PropertyMetadataObject, +): PropertyMetadataObject => { + const clonedMetadata = JSON.parse(JSON.stringify(baseMetadataObject)) as + | PropertyMetadataObject + | undefined; + + if (!clonedMetadata) { + throw new Error( + `Expected metadata to be an object, but got metadata for property array: ${JSON.stringify( + clonedMetadata, + null, + 2, + )}`, + ); + } + + const firstKey = path[0]; + + if (!firstKey) { + throw new Error("Expected path to have at least one key"); + } + + if (typeof firstKey === "number") { + throw new Error(`Expected first key to be a string, but got ${firstKey}`); + } + + const propertyMetadata = clonedMetadata.value[firstKey]; + + const secondKey = path[1]; + + const remainingKeys = path.slice(2); + + const thirdKey = remainingKeys[0]; + + if (typeof secondKey === "undefined") { + /** + * Set or delete metadata for a single value. + * This happens regardless of whether there's already metadata set at this baseUrl on the entity's properties, + * because we're just going to overwrite it or delete it regardless. + */ + if (metadata === "delete") { + delete clonedMetadata.value[firstKey]; + } else { + clonedMetadata.value[firstKey] = metadata; + } + return clonedMetadata; + } + + if (!propertyMetadata) { + if (metadata === "delete") { + /** + * We've been asked to delete metadata at a path, but we don't have any metadata for it anyway. + */ + return clonedMetadata; + } + + if (typeof secondKey === "number") { + if (thirdKey) { + if (typeof thirdKey === "number") { + throw new Error( + `Multi-dimensional arrays as property values are not yet supported`, + ); + } else { + throw new Error(`Arrays of property objects are not yet supported`); + } + } + + if (secondKey !== 0) { + throw new Error( + `Expected array index to be 0 on new array, got ${secondKey}`, + ); + } + + /** + * Set metadata for a new array + */ + clonedMetadata.value[firstKey] = { + value: [metadata], + } satisfies PropertyMetadataArray; + return clonedMetadata; + } + + /** + * Set metadata for a new property object + */ + if (typeof thirdKey !== "number") { + throw new Error("Nested property objects are not yet supported"); + } + + if (typeof thirdKey !== "undefined") { + if (thirdKey !== 0) { + throw new Error( + `Expected array index to be 0 on new array, got ${thirdKey}`, + ); + } + + /** + * This is a new property object with an array set one of its inner properties, + * i.e. metadata for entity properties that looks like this + * { + * properties: { + * [firstKey]: { + * [secondKey]: [value that `metadata` identifies] + * } + * } + * } + */ + clonedMetadata.value[firstKey] = { + value: { + [secondKey]: { value: [metadata] } satisfies PropertyMetadataArray, + }, + } satisfies PropertyMetadataObject; + } else { + /** + * This is a new property object with a single value set for one of its properties + */ + clonedMetadata.value[firstKey] = { + value: { [secondKey]: metadata }, + } satisfies PropertyMetadataObject; + } + + return clonedMetadata; + } + + /** + * If we reached here, we already have metadata set at this baseUrl on the entity's properties, + * and it isn't a single value. + */ + if (typeof secondKey === "number") { + if (!isArrayMetadata(propertyMetadata)) { + throw new Error( + `Expected property metadata to be an array at path ${JSON.stringify([firstKey, secondKey])}, but got ${JSON.stringify( + propertyMetadata, + )}`, + ); + } + + if (typeof thirdKey !== "undefined") { + if (typeof thirdKey === "number") { + throw new Error( + `Multi-dimensional arrays as property values are not yet supported`, + ); + } else { + throw new Error(`Arrays of property objects are not yet supported`); + } + } + + if (metadata === "delete") { + propertyMetadata.value.splice(secondKey, 1); + } else { + propertyMetadata.value[secondKey] = metadata; + } + return clonedMetadata; + } + + /** + * This is an existing property object + */ + if (!isObjectMetadata(propertyMetadata)) { + throw new Error( + `Expected property metadata to be an object at path ${firstKey}, but got ${JSON.stringify( + propertyMetadata, + )}`, + ); + } + + if (typeof thirdKey !== "undefined") { + if (typeof thirdKey !== "number") { + throw new Error("Nested property objects are not yet supported"); + } + + propertyMetadata.value[secondKey] ??= { + value: [], + }; + + if (!isArrayMetadata(propertyMetadata.value[secondKey])) { + throw new Error( + `Expected property metadata to be an array at path ${JSON.stringify([firstKey, secondKey])}, but got ${JSON.stringify( + propertyMetadata.value[secondKey], + )}`, + ); + } + + const propertyObjectArrayValueMetadata = + propertyMetadata.value[secondKey].value; + + if (metadata === "delete") { + propertyObjectArrayValueMetadata.splice(thirdKey, 1); + } else { + propertyObjectArrayValueMetadata[thirdKey] = metadata; + } + } else if (metadata === "delete") { + delete propertyMetadata.value[secondKey]; + } else { + propertyMetadata.value[secondKey] = metadata; + } + + return clonedMetadata; +}; + export class Entity { #entity: EntityData; @@ -696,7 +911,9 @@ export class Entity { .entityTypeIds as PropertyMap["entityTypeIds"], temporalVersioning: entity.metadata .temporalVersioning as EntityTemporalVersioningMetadata, - properties: entity.metadata.properties as PropertyMetadataObject, + properties: entity.metadata.properties as + | PropertyMetadataObject + | undefined, provenance: { ...entity.metadata.provenance, createdById: entity.metadata.provenance.createdById as CreatedById, @@ -888,7 +1105,7 @@ export class Entity { return this.#entity.metadata.properties ?? { value: {} }; } - public propertyMetadata(path: PropertyPath): PropertyMetadata["metadata"] { + public propertyMetadata(path: PropertyPath): PropertyMetadata | undefined { return path.reduce((map, key) => { if (!map || !("value" in map)) { return undefined; @@ -904,7 +1121,7 @@ export class Entity { } else { return undefined; } - }, this.#entity.metadata.properties)?.metadata; + }, this.#entity.metadata.properties); } public flattenedPropertiesMetadata(): { diff --git a/libs/@local/graph/sdk/typescript/tests/entity.test.ts b/libs/@local/graph/sdk/typescript/tests/entity.test.ts index 0a0d3918327..97335585a42 100644 --- a/libs/@local/graph/sdk/typescript/tests/entity.test.ts +++ b/libs/@local/graph/sdk/typescript/tests/entity.test.ts @@ -123,19 +123,44 @@ test("propertyMetadata access", () => { const entityInstance = new Entity(createTestEntity()); expect(entityInstance.propertyMetadata([base_url_a])).toEqual({ - confidence: 0.2, + value: { + [base_url_aa]: { + value: { + [base_url_aaa]: { + metadata: { + confidence: 0.1, + dataTypeId: + "https://blockprotocol.org/@blockprotocol/types/data-type/text/v/1", + }, + }, + }, + }, + }, + metadata: { + confidence: 0.2, + }, }); - expect( - entityInstance.propertyMetadata([base_url_a, base_url_aa]), - ).toBeUndefined(); + expect(entityInstance.propertyMetadata([base_url_a, base_url_aa])).toEqual({ + value: { + [base_url_aaa]: { + metadata: { + confidence: 0.1, + dataTypeId: + "https://blockprotocol.org/@blockprotocol/types/data-type/text/v/1", + }, + }, + }, + }); expect( entityInstance.propertyMetadata([base_url_a, base_url_aa, base_url_aaa]), ).toEqual({ - confidence: 0.1, - dataTypeId: - "https://blockprotocol.org/@blockprotocol/types/data-type/text/v/1", + metadata: { + confidence: 0.1, + dataTypeId: + "https://blockprotocol.org/@blockprotocol/types/data-type/text/v/1", + }, }); expect( @@ -148,7 +173,22 @@ test("propertyMetadata access", () => { ).toBeUndefined(); expect(entityInstance.propertyMetadata([base_url_b])).toEqual({ - confidence: 0.4, + value: [ + { + value: { + [base_url_b10b]: { + metadata: { + confidence: 0.3, + dataTypeId: + "https://blockprotocol.org/@blockprotocol/types/data-type/number/v/1", + }, + }, + }, + }, + ], + metadata: { + confidence: 0.4, + }, }); expect( @@ -162,9 +202,11 @@ test("propertyMetadata access", () => { expect( entityInstance.propertyMetadata([base_url_b, 0, base_url_b10b]), ).toEqual({ - confidence: 0.3, - dataTypeId: - "https://blockprotocol.org/@blockprotocol/types/data-type/number/v/1", + metadata: { + confidence: 0.3, + dataTypeId: + "https://blockprotocol.org/@blockprotocol/types/data-type/number/v/1", + }, }); expect( @@ -176,7 +218,17 @@ test("propertyMetadata access", () => { ]), ).toBeUndefined(); - expect(entityInstance.propertyMetadata([base_url_c])).toBeUndefined(); + expect(entityInstance.propertyMetadata([base_url_c])).toEqual({ + value: [ + { + metadata: { + confidence: 0.5, + dataTypeId: + "https://blockprotocol.org/@blockprotocol/types/data-type/object/v/1", + }, + }, + ], + }); expect(entityInstance.propertyMetadata([base_url_c, 1])).toBeUndefined(); @@ -185,9 +237,11 @@ test("propertyMetadata access", () => { ).toBeUndefined(); expect(entityInstance.propertyMetadata([base_url_c, 0])).toEqual({ - confidence: 0.5, - dataTypeId: - "https://blockprotocol.org/@blockprotocol/types/data-type/object/v/1", + metadata: { + confidence: 0.5, + dataTypeId: + "https://blockprotocol.org/@blockprotocol/types/data-type/object/v/1", + }, }); }); diff --git a/libs/@local/graph/types/typescript/src/entity.ts b/libs/@local/graph/types/typescript/src/entity.ts index 6db58293050..0ba579f4a73 100644 --- a/libs/@local/graph/types/typescript/src/entity.ts +++ b/libs/@local/graph/types/typescript/src/entity.ts @@ -78,6 +78,7 @@ export type EntityMetadata< entityTypeIds: EntityTypeIds; temporalVersioning: EntityTemporalVersioningMetadata; archived: boolean; + properties?: PropertyMetadataObject; provenance: EntityProvenance; } >; diff --git a/libs/@local/hash-isomorphic-utils/src/data-types.ts b/libs/@local/hash-isomorphic-utils/src/data-types.ts index 896667da781..bdd8e2e699e 100644 --- a/libs/@local/hash-isomorphic-utils/src/data-types.ts +++ b/libs/@local/hash-isomorphic-utils/src/data-types.ts @@ -1,10 +1,59 @@ import type { JsonValue } from "@blockprotocol/core"; import type { ClosedDataType, - DataType, + NumberConstraints, + SingleValueConstraints, SingleValueSchema, + StringConstraints, + ValueLabel, } from "@blockprotocol/type-system"; -import { getJsonSchemaTypeFromValue } from "@local/hash-subgraph/stdlib"; +import { mustHaveAtLeastOne } from "@blockprotocol/type-system"; + +type MergedNumberSchema = { + type: "number"; + const?: number; + enum?: number[]; +} & Omit & { multipleOf?: number[] }; + +type MergedStringSchema = { + type: "string"; + const?: string; + enum?: string[]; +} & Omit & { pattern?: string[] }; + +type ObjectSchema = { type: "object" }; + +type NullSchema = { type: "null" }; + +type BooleanSchema = { type: "boolean" }; + +type TupleSchema = { + prefixItems: [MergedDataTypeSingleSchema, ...MergedDataTypeSingleSchema[]]; + items: false; +}; + +type ListSchema = { + items: MergedDataTypeSingleSchema; +}; + +type ArraySchema = { type: "array" } & (TupleSchema | ListSchema); + +export type MergedValueSchema = + | MergedNumberSchema + | MergedStringSchema + | ObjectSchema + | NullSchema + | BooleanSchema + | ArraySchema; + +export type MergedDataTypeSingleSchema = { + description: string; + label?: ValueLabel; +} & MergedValueSchema; + +export type MergedDataTypeSchema = + | MergedDataTypeSingleSchema + | { anyOf: MergedDataTypeSingleSchema[] }; export type FormattedValuePart = { color: string; @@ -42,15 +91,9 @@ const createFormattedParts = ({ export const formatDataValue = ( value: JsonValue, - schema?: ClosedDataType | DataType | SingleValueSchema, + schema: MergedDataTypeSingleSchema, ): FormattedValuePart[] => { - /** - * @todo H-3374 callers should always provide a schema, because the dataTypeId will be in the entity's metadata - */ - const type = - schema && "type" in schema - ? schema.type - : getJsonSchemaTypeFromValue(value); + const { type } = schema; if (type === "null") { return createFormattedParts({ inner: "Null", schema }); @@ -65,37 +108,23 @@ export const formatDataValue = ( throw new Error("Non-array value provided for array data type"); } - if (schema && !("items" in schema)) { - // Handle the Empty List, which is a const [] with no 'items' - return [ - { - color: "#37434F", - type: "value", - text: "Empty List", - }, - ]; - } - - const isTuple = schema && "prefixItems" in schema; + const isTuple = "prefixItems" in schema; const innerValue: string = value .map((inner, index) => { - if ( - isTuple && - schema.prefixItems && - index < schema.prefixItems.length - ) { - return formatDataValue(inner, schema.prefixItems[index]); - } + if (isTuple) { + const itemSchema = schema.prefixItems[index]; + + if (!itemSchema) { + throw new Error( + `No schema for tuple item at index ${index} – value has too many items`, + ); + } - if (schema && !schema.items) { - // schema.items is false for tuple types (specifying that additional items are not allowed) - throw new Error( - "Expected 'items' schema in non-tuple array data type", - ); + return formatDataValue(inner, schema.prefixItems[index]!); } - return formatDataValue(inner, schema?.items); + return formatDataValue(inner, schema.items); }) .join(""); @@ -108,3 +137,143 @@ export const formatDataValue = ( return createFormattedParts({ inner: String(value), schema }); }; + +const transformConstraint = ( + constraint: SingleValueConstraints & { + description: string; + label?: ValueLabel; + }, +): MergedDataTypeSingleSchema => { + const { description, label, type } = constraint; + + if (type === "string") { + if ("enum" in constraint || "const" in constraint) { + return constraint; + } + + return { + ...constraint, + pattern: constraint.pattern ? [constraint.pattern] : undefined, + }; + } + if (type === "number") { + if ("enum" in constraint || "const" in constraint) { + return constraint; + } + + return { + ...constraint, + multipleOf: constraint.multipleOf ? [constraint.multipleOf] : undefined, + }; + } + + if (type === "array") { + if ("prefixItems" in constraint) { + if (!constraint.prefixItems) { + throw new Error("Expected prefixItems to be defined"); + } + + return { + ...constraint, + prefixItems: mustHaveAtLeastOne( + constraint.prefixItems.map((tupleItem) => + transformConstraint({ description, label, ...tupleItem }), + ), + ), + items: false, + }; + } + + if ("items" in constraint || !constraint.items) { + throw new Error("Expected items to be defined"); + } + + return { + ...constraint, + items: transformConstraint({ description, label, ...constraint.items }), + }; + } + + return constraint; +}; + +export const getMergedDataTypeSchema = ( + dataType: ClosedDataType, +): MergedDataTypeSchema => { + const { description, label } = dataType; + + const firstOption = dataType.allOf[0]; + + if ("anyOf" in firstOption) { + if (dataType.allOf.length > 1) { + /** + * We assume that the Graph API has reduced the constraints to a single anyOf array when closing the data type + */ + throw new Error("Expected data type to have a single anyOf constraint."); + } + + const anyOf: MergedDataTypeSingleSchema[] = []; + + for (const option of firstOption.anyOf) { + anyOf.push(transformConstraint({ description, label, ...option })); + } + + if (!anyOf[0]) { + throw new Error("Expected anyOf to have at least one constraint"); + } + + return { anyOf }; + } + + const mergedSchema: MergedDataTypeSchema = transformConstraint({ + description, + label, + ...firstOption, + }); + + if ( + mergedSchema.type === "object" || + mergedSchema.type === "array" || + mergedSchema.type === "null" || + mergedSchema.type === "boolean" + ) { + return mergedSchema; + } + + /** + * If dealing with a string or number, we need to collect all the 'pattern' and 'multipleOf' constraints, + * because the Graph API does not merge them into a single object. + * We could later deal with this at the Graph level by: + * 1. Merging all 'pattern' fields into a single RegExp, or having ClosedDataType have an array for 'pattern' + * 2. Having an array for 'multipleOf' on ClosedDataType + */ + for (const option of dataType.allOf.slice(1)) { + if ("anyOf" in option) { + throw new Error("Expected data type to have a single anyOf constraint."); + } + + if (mergedSchema.type === "string") { + if (option.type !== "string") { + throw new Error( + `Mixed primitive data types for data type: ${dataType.$id}`, + ); + } + mergedSchema.pattern ??= []; + if ("pattern" in option && option.pattern) { + mergedSchema.pattern.push(option.pattern); + } + } else { + if (option.type !== "number") { + throw new Error( + `Mixed primitive data types for data type: ${dataType.$id}`, + ); + } + mergedSchema.multipleOf ??= []; + if ("multipleOf" in option && option.multipleOf) { + mergedSchema.multipleOf.push(option.multipleOf); + } + } + } + + return mergedSchema; +}; diff --git a/libs/@local/hash-isomorphic-utils/src/generate-entity-label.ts b/libs/@local/hash-isomorphic-utils/src/generate-entity-label.ts index 41806d91288..b256e448e1e 100644 --- a/libs/@local/hash-isomorphic-utils/src/generate-entity-label.ts +++ b/libs/@local/hash-isomorphic-utils/src/generate-entity-label.ts @@ -51,9 +51,11 @@ const getFallbackLabel = ({ const entityTypeName = entityTypeTitle ?? "Entity"; + const entityUuid = extractEntityUuidFromEntityId(entityId); + return `${entityTypeName}${ includeHexChars - ? `-${extractEntityUuidFromEntityId(entityId).slice(-4, -1)}` + ? `-${entityUuid.toLowerCase() === "draft" ? "draft" : entityUuid.slice(-4, -1)}` : "" }`; }; diff --git a/libs/@local/hash-isomorphic-utils/src/graphql/scalar-mapping.ts b/libs/@local/hash-isomorphic-utils/src/graphql/scalar-mapping.ts index 7fdf35156c6..a179aec2f82 100644 --- a/libs/@local/hash-isomorphic-utils/src/graphql/scalar-mapping.ts +++ b/libs/@local/hash-isomorphic-utils/src/graphql/scalar-mapping.ts @@ -48,6 +48,8 @@ export const scalars = { "@local/hash-graph-types/entity#PropertyPatchOperation", DiffEntityInput: "@local/hash-subgraph#DiffEntityInput", DiffEntityResult: "@local/hash-graph-client#DiffEntityResult", + ValidateEntityParamsComponents: + "@local/hash-graph-client#ValidateEntityParamsComponents", Filter: "@local/hash-graph-client#Filter", diff --git a/libs/@local/hash-isomorphic-utils/src/graphql/type-defs/knowledge/entity.typedef.ts b/libs/@local/hash-isomorphic-utils/src/graphql/type-defs/knowledge/entity.typedef.ts index 86a91f18433..d4fdf24323a 100644 --- a/libs/@local/hash-isomorphic-utils/src/graphql/type-defs/knowledge/entity.typedef.ts +++ b/libs/@local/hash-isomorphic-utils/src/graphql/type-defs/knowledge/entity.typedef.ts @@ -16,6 +16,7 @@ export const entityTypedef = gql` scalar SerializedEntity scalar UserPermissions scalar UserPermissionsOnEntities + scalar ValidateEntityParamsComponents type SubgraphAndPermissions { userPermissionsOnEntities: UserPermissionsOnEntities! @@ -157,6 +158,32 @@ export const entityTypedef = gql` checkUserPermissionsOnEntity(metadata: EntityMetadata!): UserPermissions! getEntityDiffs(inputs: [DiffEntityInput!]!): [EntityDiff!]! + + """ + Validates the requested aspects of an entity + + Throws an error if the entity is invalid, with an object containing the invalid properties. + + Returns 'true' if the entity is valid + """ + validateEntity( + """ + Which aspects of the entity to validate: + - linkData: validates that linkData is present if an entity is a link, or is absent if it isn't. Default: false if draft + - linkValidation: validates that the link target is valid for the source type(s). Default: true + - numItems: that the min/max number of items in a property array is respected. + - requiredProperties: whether or not the required properties are present + """ + components: ValidateEntityParamsComponents! + """ + The proposed entity types for the entity + """ + entityTypes: [VersionedUrl!]! + """ + The proposed properties for the entity + """ + properties: PropertyObjectWithMetadata! + ): Boolean! } enum AuthorizationSubjectKind { From 42c1bfa44de70608de92f4b9ef9258415cc8dfd2 Mon Sep 17 00:00:00 2001 From: "hash-worker[bot]" <180894564+hash-worker[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:29:39 +0000 Subject: [PATCH 11/24] Update GitHub Action `codecov/codecov-action` to v5.1.1 (#5824) Co-authored-by: hash-worker[bot] <180894564+hash-worker[bot]@users.noreply.github.com> --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d96c84e422d..411fcef8bba 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -191,7 +191,7 @@ jobs: - name: Show disk usage run: df -h - - uses: codecov/codecov-action@015f24e6818733317a2da2edd6290ab26238649a # v5.0.7 + - uses: codecov/codecov-action@7f8b4b4bde536c465e797be725718b88c5d95e0e # v5.1.1 name: Upload coverage to https://app.codecov.io/gh/hashintel/hash with: flags: ${{ env.TRIMMED_PACKAGE_NAME }} @@ -367,7 +367,7 @@ jobs: - name: Show disk usage run: df -h - - uses: codecov/codecov-action@015f24e6818733317a2da2edd6290ab26238649a # v5.0.7 + - uses: codecov/codecov-action@7f8b4b4bde536c465e797be725718b88c5d95e0e # v5.1.1 name: Upload coverage to https://app.codecov.io/gh/hashintel/hash with: flags: ${{ env.TRIMMED_PACKAGE_NAME }} From f66015e8a5593beac6757438cf2383e31ce81c0f Mon Sep 17 00:00:00 2001 From: "hash-worker[bot]" <180894564+hash-worker[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:15:07 +0100 Subject: [PATCH 12/24] Update Rust crate `time` to v0.3.37 (#5787) Co-authored-by: hash-worker[bot] <180894564+hash-worker[bot]@users.noreply.github.com> Co-authored-by: Tim Diekmann <21277928+TimDiekmann@users.noreply.github.com> Co-authored-by: Tim Diekmann --- Cargo.lock | 8 +- Cargo.toml | 2 +- .../temporal-versioning/src/timestamp.rs | 10 +- tests/graph/integration/postgres/drafts.rs | 111 +++++++++++------- 4 files changed, 85 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a74cf97b7aa..788e99d74c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7531,9 +7531,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -7552,9 +7552,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", diff --git a/Cargo.toml b/Cargo.toml index 3607f1906ad..9d2f41ea602 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -133,7 +133,7 @@ sentry-types = { version = "=0.35.0", default-features = false } serde = { version = "=1.0.215", default-features = false } serde_json = { version = "=1.0.133" } text-size = { version = "=1.1.1", default-features = false } -time = { version = "=0.3.36", default-features = false } +time = { version = "=0.3.37", default-features = false } tokio = { version = "=1.42.0", default-features = false } tokio-postgres = { version = "=0.7.12", default-features = false } tokio-util = { version = "=0.7.13", default-features = false } diff --git a/libs/@local/graph/temporal-versioning/src/timestamp.rs b/libs/@local/graph/temporal-versioning/src/timestamp.rs index 3a72229c4b3..429e939dd3f 100644 --- a/libs/@local/graph/temporal-versioning/src/timestamp.rs +++ b/libs/@local/graph/temporal-versioning/src/timestamp.rs @@ -1,6 +1,6 @@ #[cfg(feature = "postgres")] use core::error::Error; -use core::{fmt, marker::PhantomData, str::FromStr}; +use core::{fmt, marker::PhantomData, ops::Sub, str::FromStr}; #[cfg(feature = "postgres")] use bytes::BytesMut; @@ -93,6 +93,14 @@ impl Timestamp { } } +impl Sub for Timestamp { + type Output = time::Duration; + + fn sub(self, rhs: Self) -> Self::Output { + self.time - rhs.time + } +} + impl FromStr for Timestamp { type Err = time::error::Parse; diff --git a/tests/graph/integration/postgres/drafts.rs b/tests/graph/integration/postgres/drafts.rs index 0f33c0b0bc9..6b54c2ad920 100644 --- a/tests/graph/integration/postgres/drafts.rs +++ b/tests/graph/integration/postgres/drafts.rs @@ -15,6 +15,7 @@ use hash_graph_types::{ owned_by_id::OwnedById, }; use pretty_assertions::assert_eq; +use time::Duration; use type_system::url::{BaseUrl, OntologyTypeVersion, VersionedUrl}; use crate::{DatabaseApi, DatabaseTestWrapper}; @@ -326,21 +327,27 @@ async fn no_initial_draft() { ); assert!(check_entity_exists(&api, entity.metadata.record_id.entity_id).await); assert!(check_entity_exists(&api, updated_entity.metadata.record_id.entity_id).await); - assert_eq!( - updated_entity + assert!( + (updated_entity .metadata .provenance .inferred - .first_non_draft_created_at_transaction_time, - Some(*undraft_transaction_time) + .first_non_draft_created_at_transaction_time + .expect("transaction time should be set") + - *undraft_transaction_time) + .abs() + < Duration::milliseconds(1) ); - assert_eq!( - updated_entity + assert!( + (updated_entity .metadata .provenance .inferred - .first_non_draft_created_at_decision_time, - Some(*undraft_decision_time) + .first_non_draft_created_at_decision_time + .expect("decision time should be set") + - *undraft_decision_time) + .abs() + < Duration::milliseconds(1) ); let updated_live_entity = api @@ -375,21 +382,27 @@ async fn no_initial_draft() { ); assert!(!check_entity_exists(&api, updated_entity.metadata.record_id.entity_id).await); assert!(check_entity_exists(&api, updated_live_entity.metadata.record_id.entity_id).await); - assert_eq!( - updated_live_entity + assert!( + (updated_live_entity .metadata .provenance .inferred - .first_non_draft_created_at_transaction_time, - Some(*undraft_transaction_time) + .first_non_draft_created_at_transaction_time + .expect("transaction time should be set") + - *undraft_transaction_time) + .abs() + < Duration::milliseconds(1) ); - assert_eq!( - updated_live_entity + assert!( + (updated_live_entity .metadata .provenance .inferred - .first_non_draft_created_at_decision_time, - Some(*undraft_decision_time) + .first_non_draft_created_at_decision_time + .expect("decision time should be set") + - *undraft_decision_time) + .abs() + < Duration::milliseconds(1) ); } } @@ -422,21 +435,27 @@ async fn multiple_drafts() { entity.metadata.temporal_versioning.transaction_time.start(); let ClosedTemporalBound::Inclusive(undraft_decision_time) = entity.metadata.temporal_versioning.decision_time.start(); - assert_eq!( - entity + assert!( + (entity .metadata .provenance .inferred - .first_non_draft_created_at_transaction_time, - Some(*undraft_transaction_time) + .first_non_draft_created_at_transaction_time + .expect("transaction time should be set") + - *undraft_transaction_time) + .abs() + < Duration::milliseconds(1) ); - assert_eq!( - entity + assert!( + (entity .metadata .provenance .inferred - .first_non_draft_created_at_decision_time, - Some(*undraft_decision_time) + .first_non_draft_created_at_decision_time + .expect("decision time should be set") + - *undraft_decision_time) + .abs() + < Duration::milliseconds(1) ); let mut drafts = Vec::new(); @@ -477,21 +496,27 @@ async fn multiple_drafts() { ); assert!(check_entity_exists(&api, entity.metadata.record_id.entity_id).await); assert!(check_entity_exists(&api, updated_entity.metadata.record_id.entity_id).await); - assert_eq!( - updated_entity + assert!( + (updated_entity .metadata .provenance .inferred - .first_non_draft_created_at_transaction_time, - Some(*undraft_transaction_time) + .first_non_draft_created_at_transaction_time + .expect("transaction time should be set") + - *undraft_transaction_time) + .abs() + < Duration::milliseconds(1) ); - assert_eq!( - updated_entity + assert!( + (updated_entity .metadata .provenance .inferred - .first_non_draft_created_at_decision_time, - Some(*undraft_decision_time) + .first_non_draft_created_at_decision_time + .expect("decision time should be set") + - *undraft_decision_time) + .abs() + < Duration::milliseconds(1) ); drafts.push(updated_entity.metadata.record_id.entity_id); } @@ -522,21 +547,27 @@ async fn multiple_drafts() { assert!(!check_entity_exists(&api, draft).await); assert!(check_entity_exists(&api, updated_live_entity.metadata.record_id.entity_id).await); - assert_eq!( - updated_live_entity + assert!( + (updated_live_entity .metadata .provenance .inferred - .first_non_draft_created_at_transaction_time, - Some(*undraft_transaction_time) + .first_non_draft_created_at_transaction_time + .expect("transaction time should be set") + - *undraft_transaction_time) + .abs() + < Duration::milliseconds(1) ); - assert_eq!( - updated_live_entity + assert!( + (updated_live_entity .metadata .provenance .inferred - .first_non_draft_created_at_decision_time, - Some(*undraft_decision_time) + .first_non_draft_created_at_decision_time + .expect("decision time should be set") + - *undraft_decision_time) + .abs() + < Duration::milliseconds(1) ); } } From 1c82ceef9e5310baf71c5accd469ea7d6ddefe13 Mon Sep 17 00:00:00 2001 From: "hash-worker[bot]" <180894564+hash-worker[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:28:45 +0000 Subject: [PATCH 13/24] Update Rust crate `tokio-stream` to v0.1.17 (#5825) Co-authored-by: hash-worker[bot] <180894564+hash-worker[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 788e99d74c9..b55693bb57c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7687,9 +7687,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", diff --git a/Cargo.toml b/Cargo.toml index 9d2f41ea602..f47ca76d75c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -220,7 +220,7 @@ test-fuzz = { version = "=7.0.1", default-features = false } test-log = { version = "=0.2.16", default-features = false } test-strategy = { version = "=0.4.0", default-features = false } thiserror = { version = "=2.0.4", default-features = false } -tokio-stream = { version = "=0.1.16", default-features = false } +tokio-stream = { version = "=0.1.17", default-features = false } tokio-test = { version = "=0.4.4", default-features = false } tower = { version = "=0.5.1", default-features = false } tower-test = { version = "=0.4.0", default-features = false } From bd19d702f39ee6d9cc6cc2e1328f3daf4faefd87 Mon Sep 17 00:00:00 2001 From: "hash-worker[bot]" <180894564+hash-worker[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:04:13 +0000 Subject: [PATCH 14/24] Update npm package `rollup` to v4.28.1 (#5826) Co-authored-by: hash-worker[bot] <180894564+hash-worker[bot]@users.noreply.github.com> Co-authored-by: vilkinsons <6226576+vilkinsons@users.noreply.github.com> --- .../type-system/typescript/package.json | 2 +- tests/hash-backend-load/package.json | 2 +- yarn.lock | 168 ++++++++++-------- 3 files changed, 91 insertions(+), 81 deletions(-) diff --git a/libs/@blockprotocol/type-system/typescript/package.json b/libs/@blockprotocol/type-system/typescript/package.json index 26b836500da..a0ec27ec541 100644 --- a/libs/@blockprotocol/type-system/typescript/package.json +++ b/libs/@blockprotocol/type-system/typescript/package.json @@ -65,7 +65,7 @@ "eslint": "8.57.0", "react": "18.2.0", "rimraf": "6.0.1", - "rollup": "4.28.0", + "rollup": "4.28.1", "tslib": "2.8.1", "typescript": "5.6.3", "vite-plugin-wasm-pack": "0.1.12", diff --git a/tests/hash-backend-load/package.json b/tests/hash-backend-load/package.json index 2841c9c8eb9..5c8620e4db5 100644 --- a/tests/hash-backend-load/package.json +++ b/tests/hash-backend-load/package.json @@ -58,7 +58,7 @@ "@types/uuid": "10.0.0", "eslint": "8.57.0", "rimraf": "6.0.1", - "rollup": "4.28.0", + "rollup": "4.28.1", "typescript": "5.6.3" } } diff --git a/yarn.lock b/yarn.lock index d8ba96c763e..ef2fd1eb465 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4206,7 +4206,7 @@ __metadata: eslint: "npm:8.57.0" react: "npm:18.2.0" rimraf: "npm:6.0.1" - rollup: "npm:4.28.0" + rollup: "npm:4.28.1" tslib: "npm:2.8.1" typescript: "npm:5.6.3" vite-plugin-wasm-pack: "npm:0.1.12" @@ -12115,128 +12115,135 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.28.0": - version: 4.28.0 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.28.0" +"@rollup/rollup-android-arm-eabi@npm:4.28.1": + version: 4.28.1 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.28.1" conditions: os=android & cpu=arm languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.28.0": - version: 4.28.0 - resolution: "@rollup/rollup-android-arm64@npm:4.28.0" +"@rollup/rollup-android-arm64@npm:4.28.1": + version: 4.28.1 + resolution: "@rollup/rollup-android-arm64@npm:4.28.1" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.28.0": - version: 4.28.0 - resolution: "@rollup/rollup-darwin-arm64@npm:4.28.0" +"@rollup/rollup-darwin-arm64@npm:4.28.1": + version: 4.28.1 + resolution: "@rollup/rollup-darwin-arm64@npm:4.28.1" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.28.0": - version: 4.28.0 - resolution: "@rollup/rollup-darwin-x64@npm:4.28.0" +"@rollup/rollup-darwin-x64@npm:4.28.1": + version: 4.28.1 + resolution: "@rollup/rollup-darwin-x64@npm:4.28.1" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-freebsd-arm64@npm:4.28.0": - version: 4.28.0 - resolution: "@rollup/rollup-freebsd-arm64@npm:4.28.0" +"@rollup/rollup-freebsd-arm64@npm:4.28.1": + version: 4.28.1 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.28.1" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-freebsd-x64@npm:4.28.0": - version: 4.28.0 - resolution: "@rollup/rollup-freebsd-x64@npm:4.28.0" +"@rollup/rollup-freebsd-x64@npm:4.28.1": + version: 4.28.1 + resolution: "@rollup/rollup-freebsd-x64@npm:4.28.1" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.28.0": - version: 4.28.0 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.28.0" +"@rollup/rollup-linux-arm-gnueabihf@npm:4.28.1": + version: 4.28.1 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.28.1" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm-musleabihf@npm:4.28.0": - version: 4.28.0 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.28.0" +"@rollup/rollup-linux-arm-musleabihf@npm:4.28.1": + version: 4.28.1 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.28.1" conditions: os=linux & cpu=arm & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.28.0": - version: 4.28.0 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.28.0" +"@rollup/rollup-linux-arm64-gnu@npm:4.28.1": + version: 4.28.1 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.28.1" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.28.0": - version: 4.28.0 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.28.0" +"@rollup/rollup-linux-arm64-musl@npm:4.28.1": + version: 4.28.1 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.28.1" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-powerpc64le-gnu@npm:4.28.0": - version: 4.28.0 - resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.28.0" +"@rollup/rollup-linux-loongarch64-gnu@npm:4.28.1": + version: 4.28.1 + resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.28.1" + conditions: os=linux & cpu=loong64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-powerpc64le-gnu@npm:4.28.1": + version: 4.28.1 + resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.28.1" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-gnu@npm:4.28.0": - version: 4.28.0 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.28.0" +"@rollup/rollup-linux-riscv64-gnu@npm:4.28.1": + version: 4.28.1 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.28.1" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-s390x-gnu@npm:4.28.0": - version: 4.28.0 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.28.0" +"@rollup/rollup-linux-s390x-gnu@npm:4.28.1": + version: 4.28.1 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.28.1" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.28.0": - version: 4.28.0 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.28.0" +"@rollup/rollup-linux-x64-gnu@npm:4.28.1": + version: 4.28.1 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.28.1" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.28.0": - version: 4.28.0 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.28.0" +"@rollup/rollup-linux-x64-musl@npm:4.28.1": + version: 4.28.1 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.28.1" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-win32-arm64-msvc@npm:4.28.0": - version: 4.28.0 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.28.0" +"@rollup/rollup-win32-arm64-msvc@npm:4.28.1": + version: 4.28.1 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.28.1" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.28.0": - version: 4.28.0 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.28.0" +"@rollup/rollup-win32-ia32-msvc@npm:4.28.1": + version: 4.28.1 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.28.1" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rollup/rollup-win32-x64-msvc@npm:4.28.0": - version: 4.28.0 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.28.0" +"@rollup/rollup-win32-x64-msvc@npm:4.28.1": + version: 4.28.1 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.28.1" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -15906,7 +15913,7 @@ __metadata: dotenv-flow: "npm:3.3.0" eslint: "npm:8.57.0" rimraf: "npm:6.0.1" - rollup: "npm:4.28.0" + rollup: "npm:4.28.1" typescript: "npm:5.6.3" uuid: "npm:9.0.1" languageName: unknown @@ -40612,28 +40619,29 @@ __metadata: languageName: node linkType: hard -"rollup@npm:4.28.0, rollup@npm:^4.20.0": - version: 4.28.0 - resolution: "rollup@npm:4.28.0" - dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.28.0" - "@rollup/rollup-android-arm64": "npm:4.28.0" - "@rollup/rollup-darwin-arm64": "npm:4.28.0" - "@rollup/rollup-darwin-x64": "npm:4.28.0" - "@rollup/rollup-freebsd-arm64": "npm:4.28.0" - "@rollup/rollup-freebsd-x64": "npm:4.28.0" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.28.0" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.28.0" - "@rollup/rollup-linux-arm64-gnu": "npm:4.28.0" - "@rollup/rollup-linux-arm64-musl": "npm:4.28.0" - "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.28.0" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.28.0" - "@rollup/rollup-linux-s390x-gnu": "npm:4.28.0" - "@rollup/rollup-linux-x64-gnu": "npm:4.28.0" - "@rollup/rollup-linux-x64-musl": "npm:4.28.0" - "@rollup/rollup-win32-arm64-msvc": "npm:4.28.0" - "@rollup/rollup-win32-ia32-msvc": "npm:4.28.0" - "@rollup/rollup-win32-x64-msvc": "npm:4.28.0" +"rollup@npm:4.28.1, rollup@npm:^4.20.0": + version: 4.28.1 + resolution: "rollup@npm:4.28.1" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.28.1" + "@rollup/rollup-android-arm64": "npm:4.28.1" + "@rollup/rollup-darwin-arm64": "npm:4.28.1" + "@rollup/rollup-darwin-x64": "npm:4.28.1" + "@rollup/rollup-freebsd-arm64": "npm:4.28.1" + "@rollup/rollup-freebsd-x64": "npm:4.28.1" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.28.1" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.28.1" + "@rollup/rollup-linux-arm64-gnu": "npm:4.28.1" + "@rollup/rollup-linux-arm64-musl": "npm:4.28.1" + "@rollup/rollup-linux-loongarch64-gnu": "npm:4.28.1" + "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.28.1" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.28.1" + "@rollup/rollup-linux-s390x-gnu": "npm:4.28.1" + "@rollup/rollup-linux-x64-gnu": "npm:4.28.1" + "@rollup/rollup-linux-x64-musl": "npm:4.28.1" + "@rollup/rollup-win32-arm64-msvc": "npm:4.28.1" + "@rollup/rollup-win32-ia32-msvc": "npm:4.28.1" + "@rollup/rollup-win32-x64-msvc": "npm:4.28.1" "@types/estree": "npm:1.0.6" fsevents: "npm:~2.3.2" dependenciesMeta: @@ -40657,6 +40665,8 @@ __metadata: optional: true "@rollup/rollup-linux-arm64-musl": optional: true + "@rollup/rollup-linux-loongarch64-gnu": + optional: true "@rollup/rollup-linux-powerpc64le-gnu": optional: true "@rollup/rollup-linux-riscv64-gnu": @@ -40677,7 +40687,7 @@ __metadata: optional: true bin: rollup: dist/bin/rollup - checksum: 10c0/98d3bc2b784eff71b997cfc2be97c00e2f100ee38adc2f8ada7b9b9ecbbc96937f667a6a247a45491807b3f2adef3c73d1f5df40d71771bff0c2d8c0cca9b369 + checksum: 10c0/2d2d0433b7cb53153a04c7b406f342f31517608dc57510e49177941b9e68c30071674b83a0292ef1d87184e5f7c6d0f2945c8b3c74963074de10c75366fe2c14 languageName: node linkType: hard From 2ff1f2dc7118189e5c692de9a57b66d18402321c Mon Sep 17 00:00:00 2001 From: "hash-worker[bot]" <180894564+hash-worker[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 22:31:00 +0000 Subject: [PATCH 15/24] Update Rust crate `lexical` to v7.0.3 (#5829) Co-authored-by: hash-worker[bot] <180894564+hash-worker[bot]@users.noreply.github.com> --- Cargo.lock | 28 ++++++++++++++-------------- Cargo.toml | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b55693bb57c..ab890f3e6db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4072,18 +4072,18 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "lexical" -version = "7.0.2" +version = "7.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c273bdc23e7f987014f5e4c7dc0e11f309c9b04fb27cbde41d067a7f53ab1b3" +checksum = "08343a397ad70e328dd1655ca08e199e64c7a3d063cfe5c9fdd1bd6b272f1fc4" dependencies = [ "lexical-core", ] [[package]] name = "lexical-core" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0431c65b318a590c1de6b8fd6e72798c92291d27762d94c9e6c37ed7a73d8458" +checksum = "06d7a061b7feb8a4b233a4d90280d13e0965c4e0181566e9ad61af98e210ca9d" dependencies = [ "lexical-parse-float", "lexical-parse-integer", @@ -4094,9 +4094,9 @@ dependencies = [ [[package]] name = "lexical-parse-float" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb17a4bdb9b418051aa59d41d65b1c9be5affab314a872e5ad7f06231fb3b4e0" +checksum = "0029bdee2a94a6c4393a86f7e6921c90f234218fa4f2154bc001c92bc51e8bf5" dependencies = [ "lexical-parse-integer", "lexical-util", @@ -4105,9 +4105,9 @@ dependencies = [ [[package]] name = "lexical-parse-integer" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5df98f4a4ab53bf8b175b363a34c7af608fe31f93cc1fb1bf07130622ca4ef61" +checksum = "440a2398a08def518ff962b69e7146246c53bad8090e2b75d95fd5a469338958" dependencies = [ "lexical-util", "static_assertions", @@ -4115,18 +4115,18 @@ dependencies = [ [[package]] name = "lexical-util" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85314db53332e5c192b6bca611fb10c114a80d1b831ddac0af1e9be1b9232ca0" +checksum = "3100209587e35b13881068ce5a41241b112e0500b4d847ba16be172829c112ff" dependencies = [ "static_assertions", ] [[package]] name = "lexical-write-float" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e7c3ad4e37db81c1cbe7cf34610340adc09c322871972f74877a712abc6c809" +checksum = "27adf08e2f91ff44ab54bbac0c4579303f0865730870f91b58c044df821f114c" dependencies = [ "lexical-util", "lexical-write-integer", @@ -4135,9 +4135,9 @@ dependencies = [ [[package]] name = "lexical-write-integer" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb89e9f6958b83258afa3deed90b5de9ef68eef090ad5086c791cd2345610162" +checksum = "8b4e5d27d742da13f013765f849efc0c4b6173e0e64404546475eb5ee0931e2c" dependencies = [ "lexical-util", "static_assertions", diff --git a/Cargo.toml b/Cargo.toml index f47ca76d75c..ad0a6a887d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -176,7 +176,7 @@ insta = { version = "=1.41.1", default-features = false } itertools = { version = "0.13.0", default-features = false } jsonschema = { version = "=0.26.1", default-features = false } justjson = { version = "=0.3.0", default-features = false } -lexical = { version = "=7.0.2", default-features = false } +lexical = { version = "=7.0.3", default-features = false } libp2p = { version = "=0.54.1", default-features = false } libp2p-stream = { version = "=0.2.0-alpha", default-features = false } logos = { version = "=0.14.3", default-features = false } From 3a3c51e7b24faca97e75acaf0ebf975a7fde3df2 Mon Sep 17 00:00:00 2001 From: "hash-worker[bot]" <180894564+hash-worker[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 22:35:54 +0000 Subject: [PATCH 16/24] Update npm package `sigma` to v3.0.0-beta.39 (#5830) Co-authored-by: hash-worker[bot] <180894564+hash-worker[bot]@users.noreply.github.com> --- apps/hash-frontend/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/hash-frontend/package.json b/apps/hash-frontend/package.json index c37c979ffa6..4180bbee408 100644 --- a/apps/hash-frontend/package.json +++ b/apps/hash-frontend/package.json @@ -119,7 +119,7 @@ "rooks": "7.14.1", "safe-stable-stringify": "2.5.0", "setimmediate": "1.0.5", - "sigma": "3.0.0-beta.38", + "sigma": "3.0.0-beta.39", "signia": "0.1.5", "signia-react": "0.1.5", "url-regex-safe": "4.0.0", diff --git a/yarn.lock b/yarn.lock index ef2fd1eb465..e78a7ca4a83 100644 --- a/yarn.lock +++ b/yarn.lock @@ -609,7 +609,7 @@ __metadata: safe-stable-stringify: "npm:2.5.0" sass: "npm:1.82.0" setimmediate: "npm:1.0.5" - sigma: "npm:3.0.0-beta.38" + sigma: "npm:3.0.0-beta.39" signia: "npm:0.1.5" signia-react: "npm:0.1.5" typescript: "npm:5.6.3" @@ -41540,13 +41540,13 @@ __metadata: languageName: node linkType: hard -"sigma@npm:3.0.0-beta.38": - version: 3.0.0-beta.38 - resolution: "sigma@npm:3.0.0-beta.38" +"sigma@npm:3.0.0-beta.39": + version: 3.0.0-beta.39 + resolution: "sigma@npm:3.0.0-beta.39" dependencies: events: "npm:^3.3.0" graphology-utils: "npm:^2.5.2" - checksum: 10c0/431467fb25539a12523edd1202d3bec83f3150a25788a0947004cff8a8ff1e536cbfa547d2c180a65a5c64d6070732eb435b641030795ff540c6b0218322722d + checksum: 10c0/42194a49990c929caa873f6026546351dbf079092e5feaca5b77150c415cfdb50b7a9ce61248b0087881ad267e896389d67227e79651cce842edb00f1a25fce5 languageName: node linkType: hard From b1c2e7034954b2f989cbdd04789d09d7f3aa0948 Mon Sep 17 00:00:00 2001 From: "hash-worker[bot]" <180894564+hash-worker[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 22:48:46 +0000 Subject: [PATCH 17/24] Update GitHub Action `returntocorp/semgrep` to v1.99.0 (#5832) Co-authored-by: hash-worker[bot] <180894564+hash-worker[bot]@users.noreply.github.com> --- .github/workflows/semgrep.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 91d881cc23d..f430aac00d6 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest container: - image: returntocorp/semgrep:1.97.0@sha256:a265d09a9ca712e6624aca09056304ce4314a695b7028d65c041dd53fd44c700 + image: returntocorp/semgrep:1.99.0@sha256:ae27024c16f7848cdbfd49c24ed0b78b13f13b85fcd7b87c679aaa8b0c0dce98 # Skip any PR created by Dependabot to avoid permission issues: if: (github.actor != 'dependabot[bot]') From f8c88ed62b408a9668c56216a71f60ee5a1fcd83 Mon Sep 17 00:00:00 2001 From: "hash-worker[bot]" <180894564+hash-worker[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 23:09:27 +0000 Subject: [PATCH 18/24] Update npm package `vite-tsconfig-paths` to v5.1.4 (#5831) Co-authored-by: hash-worker[bot] <180894564+hash-worker[bot]@users.noreply.github.com> --- apps/hash-ai-worker-ts/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/hash-ai-worker-ts/package.json b/apps/hash-ai-worker-ts/package.json index affa008a7a9..5681f1fa3c6 100644 --- a/apps/hash-ai-worker-ts/package.json +++ b/apps/hash-ai-worker-ts/package.json @@ -113,7 +113,7 @@ "eslint": "8.57.0", "rimraf": "6.0.1", "typescript": "5.6.3", - "vite-tsconfig-paths": "5.1.3", + "vite-tsconfig-paths": "5.1.4", "vitest": "2.1.8", "wait-on": "8.0.1" } diff --git a/yarn.lock b/yarn.lock index e78a7ca4a83..81ead642ef5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -356,7 +356,7 @@ __metadata: tsconfig-paths-webpack-plugin: "npm:4.2.0" tsx: "npm:4.19.2" typescript: "npm:5.6.3" - vite-tsconfig-paths: "npm:5.1.3" + vite-tsconfig-paths: "npm:5.1.4" vitest: "npm:2.1.8" wait-on: "npm:8.0.1" languageName: unknown @@ -45692,9 +45692,9 @@ __metadata: languageName: node linkType: hard -"vite-tsconfig-paths@npm:5.1.3": - version: 5.1.3 - resolution: "vite-tsconfig-paths@npm:5.1.3" +"vite-tsconfig-paths@npm:5.1.4": + version: 5.1.4 + resolution: "vite-tsconfig-paths@npm:5.1.4" dependencies: debug: "npm:^4.1.1" globrex: "npm:^0.1.2" @@ -45704,7 +45704,7 @@ __metadata: peerDependenciesMeta: vite: optional: true - checksum: 10c0/fb7480efa31fd50439f4a12c91bc953e5cc09d69fdc7eeb6ffff7cc796bc2c1f2c617c3abfdcbf5d7414848076cea9deb60bc002142f93b6e3131e5458760710 + checksum: 10c0/6228f23155ea25d92b1e1702284cf8dc52ad3c683c5ca691edd5a4c82d2913e7326d00708cef1cbfde9bb226261df0e0a12e03ef1d43b6a92d8f02b483ef37e3 languageName: node linkType: hard From a05c54896b82cebd583952a17b6dbce7c88e5097 Mon Sep 17 00:00:00 2001 From: "hash-worker[bot]" <180894564+hash-worker[bot]@users.noreply.github.com> Date: Sat, 7 Dec 2024 02:52:29 +0000 Subject: [PATCH 19/24] Update GitHub Action `taiki-e/install-action` to v2.46.4 (#5828) Co-authored-by: hash-worker[bot] <180894564+hash-worker[bot]@users.noreply.github.com> --- .github/actions/warm-up-repo/action.yml | 2 +- .github/workflows/bench.yml | 4 ++-- .github/workflows/lint.yml | 2 +- .github/workflows/test.yml | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/actions/warm-up-repo/action.yml b/.github/actions/warm-up-repo/action.yml index d809d07bce7..4d18482f8bd 100644 --- a/.github/actions/warm-up-repo/action.yml +++ b/.github/actions/warm-up-repo/action.yml @@ -17,7 +17,7 @@ runs: # cache: yarn ## Currently disabled because of frequent timeouts - name: Install WASM tools - uses: taiki-e/install-action@6aa8b420a527920162babdd71d780f9e5b3b4bc8 # v2.46.1 + uses: taiki-e/install-action@acf70b3a1ed953bccebc8c5d80cfdb16ec8ccc36 # v2.46.4 with: tool: wasm-pack@0.12.1 diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 563dc76a3bc..3da69521686 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -99,7 +99,7 @@ jobs: - name: Install Rust tools if: steps.benches.outputs.has-rust == 'true' - uses: taiki-e/install-action@6aa8b420a527920162babdd71d780f9e5b3b4bc8 # v2.46.1 + uses: taiki-e/install-action@acf70b3a1ed953bccebc8c5d80cfdb16ec8ccc36 # v2.46.4 with: tool: just@1.34.0,critcmp@0.1.8 @@ -251,7 +251,7 @@ jobs: - name: Install Rust tools if: steps.benches.outputs.has-rust == 'true' - uses: taiki-e/install-action@6aa8b420a527920162babdd71d780f9e5b3b4bc8 # v2.46.1 + uses: taiki-e/install-action@acf70b3a1ed953bccebc8c5d80cfdb16ec8ccc36 # v2.46.4 with: tool: just@1.34.0,critcmp@0.1.8 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index da0a5121e2a..0ef76022b95 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -111,7 +111,7 @@ jobs: - name: Install Rust tools if: always() && steps.lints.outputs.has-rust == 'true' - uses: taiki-e/install-action@6aa8b420a527920162babdd71d780f9e5b3b4bc8 # v2.46.1 + uses: taiki-e/install-action@acf70b3a1ed953bccebc8c5d80cfdb16ec8ccc36 # v2.46.4 with: tool: just@1.34.0,cargo-hack@0.6.30,clippy-sarif@0.6.5,sarif-fmt@0.6.5 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 411fcef8bba..4a3cff14461 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -163,7 +163,7 @@ jobs: - name: Install Rust tools if: always() && steps.tests.outputs.has-rust == 'true' - uses: taiki-e/install-action@6aa8b420a527920162babdd71d780f9e5b3b4bc8 # v2.46.1 + uses: taiki-e/install-action@acf70b3a1ed953bccebc8c5d80cfdb16ec8ccc36 # v2.46.4 with: tool: just@1.34.0,cargo-hack@0.6.30,cargo-nextest@0.9.72,cargo-llvm-cov@0.6.11 @@ -281,7 +281,7 @@ jobs: - name: Install Rust tools if: steps.tests.outputs.has-rust == 'true' - uses: taiki-e/install-action@6aa8b420a527920162babdd71d780f9e5b3b4bc8 # v2.46.1 + uses: taiki-e/install-action@acf70b3a1ed953bccebc8c5d80cfdb16ec8ccc36 # v2.46.4 with: tool: just@1.34.0,cargo-hack@0.6.30,cargo-nextest@0.9.72,cargo-llvm-cov@0.6.11 From 5cb33ae5a12336c208c357bdc868930e211d5643 Mon Sep 17 00:00:00 2001 From: "hash-worker[bot]" <180894564+hash-worker[bot]@users.noreply.github.com> Date: Sat, 7 Dec 2024 13:46:54 +0000 Subject: [PATCH 20/24] Update Rust crate `logos` to v0.15.0 (#5833) Co-authored-by: hash-worker[bot] <180894564+hash-worker[bot]@users.noreply.github.com> --- Cargo.lock | 12 ++++++------ Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab890f3e6db..2860ae84e4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4580,18 +4580,18 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "logos" -version = "0.14.3" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b6aa86787fd2da255f97a4425799c8d1fd39951f5798a1192fc1b956581f605" +checksum = "ab6f536c1af4c7cc81edf73da1f8029896e7e1e16a219ef09b184e76a296f3db" dependencies = [ "logos-derive", ] [[package]] name = "logos-codegen" -version = "0.14.3" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f3303189202bb8a052bcd93d66b6c03e6fe70d9c7c47c0ea5e974955e54c876" +checksum = "189bbfd0b61330abea797e5e9276408f2edbe4f822d7ad08685d67419aafb34e" dependencies = [ "beef", "fnv", @@ -4605,9 +4605,9 @@ dependencies = [ [[package]] name = "logos-derive" -version = "0.14.3" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "774a1c225576486e4fdf40b74646f672c542ca3608160d348749693ae9d456e6" +checksum = "ebfe8e1a19049ddbfccbd14ac834b215e11b85b90bab0c2dba7c7b92fb5d5cba" dependencies = [ "logos-codegen", ] diff --git a/Cargo.toml b/Cargo.toml index ad0a6a887d1..aa934db8b10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -179,7 +179,7 @@ justjson = { version = "=0.3.0", default-features = false } lexical = { version = "=7.0.3", default-features = false } libp2p = { version = "=0.54.1", default-features = false } libp2p-stream = { version = "=0.2.0-alpha", default-features = false } -logos = { version = "=0.14.3", default-features = false } +logos = { version = "=0.15.0", default-features = false } memchr = { version = "=2.7.4", default-features = false } mimalloc = { version = "=0.1.43", default-features = false } mime = { version = "=0.3.17", default-features = false } From f37f0cd585929f3d43dc74c1ed77013b24c89389 Mon Sep 17 00:00:00 2001 From: "hash-worker[bot]" <180894564+hash-worker[bot]@users.noreply.github.com> Date: Sat, 7 Dec 2024 14:18:40 +0000 Subject: [PATCH 21/24] Update Rust crate `ansi-to-html` to v0.2.2 (#5835) Co-authored-by: hash-worker[bot] <180894564+hash-worker[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2860ae84e4d..03a0fb357ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -110,9 +110,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "ansi-to-html" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d73c455ae09fa2223a75114789f30ad605e9e297f79537953523366c05995f5f" +checksum = "12e283a4fc285735ef99577e81a125f738429516161ac33977e466d0d8d40764" dependencies = [ "regex", "thiserror 1.0.69", diff --git a/Cargo.toml b/Cargo.toml index aa934db8b10..91baea6ec27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -148,7 +148,7 @@ utoipa = { version = "=4.2.3", default-features = false } uuid = { version = "=1.11.0", default-features = false } # Shared third-party dependencies -ansi-to-html = { version = "=0.2.1", default-features = false } +ansi-to-html = { version = "=0.2.2", default-features = false } anstyle = { version = "=1.0.10", default-features = false } anstyle-yansi = { version = "=2.0.2", default-features = false } approx = { version = "=0.5.1", default-features = false } From 5f4b811c28b2e8986adb986d3fd684207538062a Mon Sep 17 00:00:00 2001 From: Tim Diekmann <21277928+TimDiekmann@users.noreply.github.com> Date: Sat, 7 Dec 2024 15:35:08 +0100 Subject: [PATCH 22/24] H-3741: Allow querying children of resolved entity types as well (#5834) --- .../graphql/resolvers/ontology/entity-type.ts | 2 +- .../get-expected-types-of-property-type.ts | 2 +- libs/@local/graph/api/openapi/openapi.json | 37 ++++++- libs/@local/graph/api/src/rest/data_type.rs | 2 + libs/@local/graph/api/src/rest/entity_type.rs | 5 +- .../store/postgres/knowledge/entity/mod.rs | 102 +++++++++--------- .../store/postgres/ontology/entity_type.rs | 77 +++++++++---- .../@local/graph/store/src/entity_type/mod.rs | 12 +-- .../graph/store/src/entity_type/store.rs | 32 +++++- .../graph/types/typescript/src/ontology.ts | 9 +- .../graph/knowledge/primitive/entity.test.ts | 2 +- .../ontology/primitive/entity-type.test.ts | 2 +- 12 files changed, 195 insertions(+), 89 deletions(-) diff --git a/apps/hash-api/src/graphql/resolvers/ontology/entity-type.ts b/apps/hash-api/src/graphql/resolvers/ontology/entity-type.ts index 694fb51c890..5a7721dc164 100644 --- a/apps/hash-api/src/graphql/resolvers/ontology/entity-type.ts +++ b/apps/hash-api/src/graphql/resolvers/ontology/entity-type.ts @@ -163,7 +163,7 @@ export const getClosedMultiEntityTypeResolver: ResolverFn< { entityTypeIds, // All references to other types are resolved, and those types provided under 'definitions' in the response - includeResolved: true, + includeResolved: "resolved", includeDrafts: includeDrafts ?? false, temporalAxes: includeArchived ? fullTransactionTimeAxis diff --git a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/use-rows/generate-property-rows-from-entity/get-expected-types-of-property-type.ts b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/use-rows/generate-property-rows-from-entity/get-expected-types-of-property-type.ts index b9cc4008b07..315560c0e60 100644 --- a/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/use-rows/generate-property-rows-from-entity/get-expected-types-of-property-type.ts +++ b/apps/hash-frontend/src/pages/[shortname]/entities/[entity-uuid].page/entity-editor/properties-section/property-table/use-rows/generate-property-rows-from-entity/get-expected-types-of-property-type.ts @@ -30,7 +30,7 @@ const getReferencedDataTypes = ( for (const value of propertyValues) { if ("$ref" in value) { - types.push(getDataType(value, definitions)); + types.push(getDataType(value, definitions).schema); } } diff --git a/libs/@local/graph/api/openapi/openapi.json b/libs/@local/graph/api/openapi/openapi.json index 9d2343d7d01..9cb5652e3ce 100644 --- a/libs/@local/graph/api/openapi/openapi.json +++ b/libs/@local/graph/api/openapi/openapi.json @@ -3091,6 +3091,24 @@ "ClosedDataType": { "$ref": "./models/closed_data_type.json" }, + "ClosedDataTypeDefinition": { + "type": "object", + "required": [ + "schema", + "parents" + ], + "properties": { + "parents": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VersionedUrl" + } + }, + "schema": { + "$ref": "#/components/schemas/ClosedDataType" + } + } + }, "ClosedEntityType": { "$ref": "./models/closed_entity_type.json" }, @@ -4658,7 +4676,7 @@ "dataTypes": { "type": "object", "additionalProperties": { - "$ref": "#/components/schemas/ClosedDataType" + "$ref": "#/components/schemas/ClosedDataTypeDefinition" } }, "entityTypes": { @@ -5140,7 +5158,12 @@ "type": "boolean" }, "includeResolved": { - "type": "boolean" + "allOf": [ + { + "$ref": "#/components/schemas/IncludeResolvedEntityTypeOption" + } + ], + "nullable": true }, "temporalAxes": { "$ref": "#/components/schemas/QueryTemporalAxesUnresolved" @@ -5933,7 +5956,15 @@ "type": "string", "enum": [ "closed", - "resolved" + "resolved", + "resolvedWithDataTypeChildren" + ] + }, + "IncludeResolvedEntityTypeOption": { + "type": "string", + "enum": [ + "resolved", + "resolvedWithDataTypeChildren" ] }, "InferredEntityProvenance": { diff --git a/libs/@local/graph/api/src/rest/data_type.rs b/libs/@local/graph/api/src/rest/data_type.rs index d89c8bd6d55..afdce654cd5 100644 --- a/libs/@local/graph/api/src/rest/data_type.rs +++ b/libs/@local/graph/api/src/rest/data_type.rs @@ -29,6 +29,7 @@ use hash_graph_store::{ GetDataTypeSubgraphParams, GetDataTypesParams, GetDataTypesResponse, UnarchiveDataTypeParams, UpdateDataTypeEmbeddingParams, UpdateDataTypesParams, }, + entity_type::ClosedDataTypeDefinition, pool::StorePool, query::ConflictBehavior, }; @@ -98,6 +99,7 @@ use crate::rest::{ GetDataTypeSubgraphResponse, ArchiveDataTypeParams, UnarchiveDataTypeParams, + ClosedDataTypeDefinition, ConversionDefinition, ConversionExpression, diff --git a/libs/@local/graph/api/src/rest/entity_type.rs b/libs/@local/graph/api/src/rest/entity_type.rs index 82787d02b31..b16612625a8 100644 --- a/libs/@local/graph/api/src/rest/entity_type.rs +++ b/libs/@local/graph/api/src/rest/entity_type.rs @@ -30,8 +30,8 @@ use hash_graph_store::{ ArchiveEntityTypeParams, CreateEntityTypeParams, EntityTypeQueryToken, EntityTypeResolveDefinitions, EntityTypeStore as _, GetClosedMultiEntityTypeParams, GetClosedMultiEntityTypeResponse, GetEntityTypeSubgraphParams, GetEntityTypesParams, - GetEntityTypesResponse, IncludeEntityTypeOption, UnarchiveEntityTypeParams, - UpdateEntityTypeEmbeddingParams, UpdateEntityTypesParams, + GetEntityTypesResponse, IncludeEntityTypeOption, IncludeResolvedEntityTypeOption, + UnarchiveEntityTypeParams, UpdateEntityTypeEmbeddingParams, UpdateEntityTypesParams, }, pool::StorePool, query::ConflictBehavior, @@ -111,6 +111,7 @@ use crate::rest::{ GetEntityTypeSubgraphResponse, ArchiveEntityTypeParams, UnarchiveEntityTypeParams, + IncludeResolvedEntityTypeOption, ) ), tags( diff --git a/libs/@local/graph/postgres-store/src/store/postgres/knowledge/entity/mod.rs b/libs/@local/graph/postgres-store/src/store/postgres/knowledge/entity/mod.rs index 1daf5e4b215..a9daf51ca66 100644 --- a/libs/@local/graph/postgres-store/src/store/postgres/knowledge/entity/mod.rs +++ b/libs/@local/graph/postgres-store/src/store/postgres/knowledge/entity/mod.rs @@ -689,31 +689,34 @@ where } else { None }, - #[expect( - clippy::if_then_some_else_none, - reason = "False positive, use of `await`" - )] - definitions: if params.include_entity_types - == Some(IncludeEntityTypeOption::Resolved) - { - let entity_type_uuids = root_entities - .iter() - .flat_map(|(entity, _)| { - entity - .metadata - .entity_type_ids - .iter() - .map(EntityTypeUuid::from_url) - }) - .collect::>() - .into_iter() - .collect::>(); + definitions: match params.include_entity_types { Some( - self.get_entity_type_resolve_definitions(actor_id, &entity_type_uuids) + IncludeEntityTypeOption::Resolved + | IncludeEntityTypeOption::ResolvedWithDataTypeChildren, + ) => { + let entity_type_uuids = root_entities + .iter() + .flat_map(|(entity, _)| { + entity + .metadata + .entity_type_ids + .iter() + .map(EntityTypeUuid::from_url) + }) + .collect::>() + .into_iter() + .collect::>(); + Some( + self.get_entity_type_resolve_definitions( + actor_id, + &entity_type_uuids, + params.include_entity_types + == Some(IncludeEntityTypeOption::ResolvedWithDataTypeChildren), + ) .await?, - ) - } else { - None + ) + } + None | Some(IncludeEntityTypeOption::Closed) => None, }, entities: root_entities .into_iter() @@ -1385,33 +1388,36 @@ where } else { None }, - #[expect( - clippy::if_then_some_else_none, - reason = "False positive, use of `await`" - )] - definitions: if params.include_entity_types - == Some(IncludeEntityTypeOption::Resolved) - { - let entity_type_uuids = subgraph - .vertices - .entities - .values() - .flat_map(|entity| { - entity - .metadata - .entity_type_ids - .iter() - .map(EntityTypeUuid::from_url) - }) - .collect::>() - .into_iter() - .collect::>(); + definitions: match params.include_entity_types { Some( - self.get_entity_type_resolve_definitions(actor_id, &entity_type_uuids) + IncludeEntityTypeOption::Resolved + | IncludeEntityTypeOption::ResolvedWithDataTypeChildren, + ) => { + let entity_type_uuids = subgraph + .vertices + .entities + .values() + .flat_map(|entity| { + entity + .metadata + .entity_type_ids + .iter() + .map(EntityTypeUuid::from_url) + }) + .collect::>() + .into_iter() + .collect::>(); + Some( + self.get_entity_type_resolve_definitions( + actor_id, + &entity_type_uuids, + params.include_entity_types + == Some(IncludeEntityTypeOption::ResolvedWithDataTypeChildren), + ) .await?, - ) - } else { - None + ) + } + None | Some(IncludeEntityTypeOption::Closed) => None, }, subgraph, cursor, diff --git a/libs/@local/graph/postgres-store/src/store/postgres/ontology/entity_type.rs b/libs/@local/graph/postgres-store/src/store/postgres/ontology/entity_type.rs index 555e2094d53..8067d91be01 100644 --- a/libs/@local/graph/postgres-store/src/store/postgres/ontology/entity_type.rs +++ b/libs/@local/graph/postgres-store/src/store/postgres/ontology/entity_type.rs @@ -14,8 +14,8 @@ use hash_graph_authorization::{ }; use hash_graph_store::{ entity_type::{ - ArchiveEntityTypeParams, CountEntityTypesParams, CreateEntityTypeParams, - EntityTypeQueryPath, EntityTypeResolveDefinitions, EntityTypeStore, + ArchiveEntityTypeParams, ClosedDataTypeDefinition, CountEntityTypesParams, + CreateEntityTypeParams, EntityTypeQueryPath, EntityTypeResolveDefinitions, EntityTypeStore, GetClosedMultiEntityTypeParams, GetClosedMultiEntityTypeResponse, GetEntityTypeSubgraphParams, GetEntityTypeSubgraphResponse, GetEntityTypesParams, GetEntityTypesResponse, IncludeEntityTypeOption, UnarchiveEntityTypeParams, @@ -52,10 +52,10 @@ use tracing::{Instrument as _, instrument}; use type_system::{ Valid, Validator as _, schema::{ - ClosedDataType, ClosedEntityType, ClosedMultiEntityType, DataTypeUuid, EntityType, - EntityTypeResolveData, EntityTypeToPropertyTypeEdge, EntityTypeUuid, EntityTypeValidator, - InheritanceDepth, OntologyTypeResolver, OntologyTypeUuid, PartialEntityType, - PropertyTypeUuid, + ClosedDataType, ClosedEntityType, ClosedMultiEntityType, DataType, DataTypeUuid, + EntityType, EntityTypeResolveData, EntityTypeToPropertyTypeEdge, EntityTypeUuid, + EntityTypeValidator, InheritanceDepth, OntologyTypeResolver, OntologyTypeUuid, + PartialEntityType, PropertyTypeUuid, }, url::{OntologyTypeVersion, VersionedUrl}, }; @@ -121,6 +121,7 @@ where &self, actor_id: AccountId, entity_types: &[EntityTypeUuid], + include_data_type_children: bool, ) -> Result> { let mut definitions = EntityTypeResolveDefinitions::default(); let rows = self @@ -214,18 +215,42 @@ where .insert(VersionedUrl::from(vertex_id), property_type.schema); } + let query = if include_data_type_children { + " + SELECT DISTINCT ON (ontology_id) schema, closed_schema + FROM data_type_inherits_from + JOIN data_types + ON ontology_id = source_data_type_ontology_id + OR ontology_id = target_data_type_ontology_id + WHERE target_data_type_ontology_id = ANY($1); + " + } else { + " + SELECT schema, closed_schema + FROM data_types + WHERE ontology_id = ANY($1); + " + }; + definitions.data_types.extend( self.as_client() - .query( - "SELECT closed_schema FROM data_types WHERE ontology_id = ANY($1);", - &[&data_type_uuids], - ) + .query(query, &[&data_type_uuids]) .await .change_context(QueryError)? .into_iter() .map(|row| { - let schema: Valid = row.get(0); - (schema.id.clone(), schema.into_inner()) + let parents = row + .get::<_, Valid>(0) + .into_inner() + .all_of + .into_iter() + .map(|reference| reference.url) + .collect(); + let schema = row.get::<_, Valid>(1).into_inner(); + (schema.id.clone(), ClosedDataTypeDefinition { + schema, + parents, + }) }), ); @@ -995,11 +1020,20 @@ where response.closed_entity_types = Some(self.get_closed_entity_types(&ids).await?); - if include_entity_types == IncludeEntityTypeOption::Resolved { - response.definitions = Some( - self.get_entity_type_resolve_definitions(actor_id, &ids) - .await?, - ); + match include_entity_types { + IncludeEntityTypeOption::Closed => {} + IncludeEntityTypeOption::Resolved => { + response.definitions = Some( + self.get_entity_type_resolve_definitions(actor_id, &ids, false) + .await?, + ); + } + IncludeEntityTypeOption::ResolvedWithDataTypeChildren => { + response.definitions = Some( + self.get_entity_type_resolve_definitions(actor_id, &ids, true) + .await?, + ); + } } } @@ -1029,11 +1063,10 @@ where after: None, limit: None, include_count: false, - include_entity_types: Some(if params.include_resolved { - IncludeEntityTypeOption::Resolved - } else { - IncludeEntityTypeOption::Closed - }), + include_entity_types: Some(params.include_resolved.map_or( + IncludeEntityTypeOption::Closed, + IncludeEntityTypeOption::from, + )), include_web_ids: false, include_edition_created_by_ids: false, }) diff --git a/libs/@local/graph/store/src/entity_type/mod.rs b/libs/@local/graph/store/src/entity_type/mod.rs index ac59c95c25f..ebbae3a0ed2 100644 --- a/libs/@local/graph/store/src/entity_type/mod.rs +++ b/libs/@local/graph/store/src/entity_type/mod.rs @@ -2,12 +2,12 @@ pub(crate) use self::query::EntityTypeQueryPathVisitor; pub use self::{ query::{EntityTypeQueryPath, EntityTypeQueryToken}, store::{ - ArchiveEntityTypeParams, CountEntityTypesParams, CreateEntityTypeParams, - EntityTypeResolveDefinitions, EntityTypeStore, GetClosedMultiEntityTypeParams, - GetClosedMultiEntityTypeResponse, GetEntityTypeSubgraphParams, - GetEntityTypeSubgraphResponse, GetEntityTypesParams, GetEntityTypesResponse, - IncludeEntityTypeOption, UnarchiveEntityTypeParams, UpdateEntityTypeEmbeddingParams, - UpdateEntityTypesParams, + ArchiveEntityTypeParams, ClosedDataTypeDefinition, CountEntityTypesParams, + CreateEntityTypeParams, EntityTypeResolveDefinitions, EntityTypeStore, + GetClosedMultiEntityTypeParams, GetClosedMultiEntityTypeResponse, + GetEntityTypeSubgraphParams, GetEntityTypeSubgraphResponse, GetEntityTypesParams, + GetEntityTypesResponse, IncludeEntityTypeOption, IncludeResolvedEntityTypeOption, + UnarchiveEntityTypeParams, UpdateEntityTypeEmbeddingParams, UpdateEntityTypesParams, }, }; diff --git a/libs/@local/graph/store/src/entity_type/store.rs b/libs/@local/graph/store/src/entity_type/store.rs index 38871cab0f0..f8c934f9afc 100644 --- a/libs/@local/graph/store/src/entity_type/store.rs +++ b/libs/@local/graph/store/src/entity_type/store.rs @@ -114,6 +114,26 @@ pub struct GetEntityTypesParams<'p> { pub enum IncludeEntityTypeOption { Closed, Resolved, + ResolvedWithDataTypeChildren, +} + +impl From for IncludeEntityTypeOption { + fn from(value: IncludeResolvedEntityTypeOption) -> Self { + match value { + IncludeResolvedEntityTypeOption::Resolved => Self::Resolved, + IncludeResolvedEntityTypeOption::ResolvedWithDataTypeChildren => { + Self::ResolvedWithDataTypeChildren + } + } + } +} + +#[derive(Debug, Serialize)] +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[serde(rename_all = "camelCase")] +pub struct ClosedDataTypeDefinition { + pub schema: ClosedDataType, + pub parents: Vec, } #[derive(Debug, Default, Serialize)] @@ -121,7 +141,7 @@ pub enum IncludeEntityTypeOption { #[serde(rename_all = "camelCase")] #[expect(clippy::struct_field_names)] pub struct EntityTypeResolveDefinitions { - pub data_types: HashMap, + pub data_types: HashMap, pub property_types: HashMap, pub entity_types: HashMap, } @@ -154,6 +174,14 @@ pub struct GetEntityTypesResponse { pub edition_created_by_ids: Option>, } +#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)] +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[serde(rename_all = "camelCase")] +pub enum IncludeResolvedEntityTypeOption { + Resolved, + ResolvedWithDataTypeChildren, +} + #[derive(Debug, Deserialize)] #[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -162,7 +190,7 @@ pub struct GetClosedMultiEntityTypeParams { pub temporal_axes: QueryTemporalAxesUnresolved, pub include_drafts: bool, #[serde(default)] - pub include_resolved: bool, + pub include_resolved: Option, } #[derive(Debug, Serialize)] diff --git a/libs/@local/graph/types/typescript/src/ontology.ts b/libs/@local/graph/types/typescript/src/ontology.ts index 508693a9825..a7089645774 100644 --- a/libs/@local/graph/types/typescript/src/ontology.ts +++ b/libs/@local/graph/types/typescript/src/ontology.ts @@ -172,10 +172,15 @@ export type ClosedMultiEntityTypesRootMap = { [key: string]: ClosedMultiEntityTypeMap; }; +export interface ClosedDataTypeDefinition { + schema: ClosedDataType; + parents: VersionedUrl[]; +} + export type ClosedMultiEntityTypesDefinitions = Subtype< GetClosedMultiEntityTypeResponseDefinitions, { - dataTypes: { [key: VersionedUrl]: ClosedDataType }; + dataTypes: { [key: VersionedUrl]: ClosedDataTypeDefinition }; entityTypes: { [key: VersionedUrl]: PartialEntityType }; propertyTypes: { [key: VersionedUrl]: PropertyType }; } @@ -188,7 +193,7 @@ export type PartialEntityType = Omit & { export type EntityTypeResolveDefinitions = Subtype< EntityTypeResolveDefinitionsGraphApi, { - dataTypes: Record; + dataTypes: Record; propertyTypes: Record; entityTypes: Record; } diff --git a/tests/hash-backend-integration/src/tests/graph/knowledge/primitive/entity.test.ts b/tests/hash-backend-integration/src/tests/graph/knowledge/primitive/entity.test.ts index d88b7d5e6ba..992571ed64e 100644 --- a/tests/hash-backend-integration/src/tests/graph/knowledge/primitive/entity.test.ts +++ b/tests/hash-backend-integration/src/tests/graph/knowledge/primitive/entity.test.ts @@ -330,7 +330,7 @@ describe("Entity CRU", () => { entityTypeIds: entity.metadata.entityTypeIds, temporalAxes: currentTimeInstantTemporalAxes, includeDrafts: false, - includeResolved: true, + includeResolved: "resolved", }, ); diff --git a/tests/hash-backend-integration/src/tests/graph/ontology/primitive/entity-type.test.ts b/tests/hash-backend-integration/src/tests/graph/ontology/primitive/entity-type.test.ts index 8f4dea14a2a..a5c53397ecb 100644 --- a/tests/hash-backend-integration/src/tests/graph/ontology/primitive/entity-type.test.ts +++ b/tests/hash-backend-integration/src/tests/graph/ontology/primitive/entity-type.test.ts @@ -389,7 +389,7 @@ describe("Entity type CRU", () => { ], temporalAxes: currentTimeInstantTemporalAxes, includeDrafts: false, - includeResolved: true, + includeResolved: "resolved", }); // It's not specified how `required` is ordered, so we need to sort it before comparing From 417362f768fb498218852086c1e5d4220cb43b42 Mon Sep 17 00:00:00 2001 From: Tim Diekmann <21277928+TimDiekmann@users.noreply.github.com> Date: Sat, 7 Dec 2024 15:37:42 +0100 Subject: [PATCH 23/24] H-3371, H-3722: Overhaul return behavior for entity validation (#5820) --- Cargo.lock | 2 + .../src/graph/knowledge/primitive/entity.ts | 21 - .../resolvers/knowledge/entity/entity.ts | 12 +- .../rust/src/schema/entity_type/closed.rs | 14 + .../rust/src/schema/entity_type/mod.rs | 7 +- .../type-system/rust/src/schema/mod.rs | 1 + libs/@local/graph/api/Cargo.toml | 1 + .../api/openapi/models/multi_report.json | 9 + .../graph/api/openapi/models/report.json | 10 + .../openapi/models/report_context_info.json | 23 ++ libs/@local/graph/api/openapi/openapi.json | 360 ++++++++++++++++- libs/@local/graph/api/package.json | 1 + libs/@local/graph/api/src/rest/entity.rs | 37 +- .../src/rest/json_schemas/multi_report.json | 9 + .../api/src/rest/json_schemas/report.json | 10 + .../json_schemas/report_context_info.json | 23 ++ libs/@local/graph/api/src/rest/mod.rs | 2 + libs/@local/graph/api/src/rest/status.rs | 36 +- .../src/snapshot/entity/batch.rs | 48 ++- .../store/postgres/knowledge/entity/mod.rs | 188 +++++---- .../postgres-store/src/store/validation.rs | 76 +--- .../@local/graph/sdk/typescript/src/entity.ts | 5 +- libs/@local/graph/store/Cargo.toml | 2 +- libs/@local/graph/store/src/entity/mod.rs | 8 + libs/@local/graph/store/src/entity/store.rs | 6 +- .../store/src/entity/validation_report.rs | 208 ++++++++++ libs/@local/graph/type-fetcher/src/store.rs | 8 +- .../rust/src/knowledge/property/visitor.rs | 22 +- .../graph/types/rust/src/ontology/mod.rs | 39 +- libs/@local/graph/validation/Cargo.toml | 19 +- .../graph/validation/src/entity_type.rs | 370 ++++++++---------- libs/@local/graph/validation/src/lib.rs | 45 +-- libs/@local/graph/validation/src/property.rs | 14 +- libs/chonky/README.md | 4 +- tests/graph/http/.justfile | 19 - tests/graph/http/test.sh | 18 +- tests/graph/http/tests/friendship.http | 9 +- tests/graph/http/tests/link-inheritance.http | 10 +- tests/graph/integration/postgres/lib.rs | 8 +- yarn.lock | 1 + 40 files changed, 1126 insertions(+), 579 deletions(-) create mode 100644 libs/@local/graph/api/openapi/models/multi_report.json create mode 100644 libs/@local/graph/api/openapi/models/report.json create mode 100644 libs/@local/graph/api/openapi/models/report_context_info.json create mode 100644 libs/@local/graph/api/src/rest/json_schemas/multi_report.json create mode 100644 libs/@local/graph/api/src/rest/json_schemas/report.json create mode 100644 libs/@local/graph/api/src/rest/json_schemas/report_context_info.json create mode 100644 libs/@local/graph/store/src/entity/validation_report.rs delete mode 100755 tests/graph/http/.justfile diff --git a/Cargo.lock b/Cargo.lock index 03a0fb357ed..18d640aa71a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2842,6 +2842,7 @@ dependencies = [ "hash-graph-type-defs", "hash-graph-type-fetcher", "hash-graph-types", + "hash-graph-validation", "hash-status", "hash-temporal-client", "http 1.2.0", @@ -3129,6 +3130,7 @@ dependencies = [ name = "hash-graph-validation" version = "0.0.0" dependencies = [ + "derive_more 1.0.0", "error-stack", "futures", "hash-graph-store", diff --git a/apps/hash-api/src/graph/knowledge/primitive/entity.ts b/apps/hash-api/src/graph/knowledge/primitive/entity.ts index 7942a671bf8..7ee8f66d90e 100644 --- a/apps/hash-api/src/graph/knowledge/primitive/entity.ts +++ b/apps/hash-api/src/graph/knowledge/primitive/entity.ts @@ -13,7 +13,6 @@ import type { GraphResolveDepths, ModifyRelationshipOperation, } from "@local/hash-graph-client"; -import type { ValidateEntityParamsComponents } from "@local/hash-graph-client/api"; import type { CreateEntityParameters } from "@local/hash-graph-sdk/entity"; import { Entity, LinkEntity } from "@local/hash-graph-sdk/entity"; import type { @@ -25,7 +24,6 @@ import type { EntityProperties, LinkData, PropertyObject, - PropertyObjectWithMetadata, PropertyPatchOperation, } from "@local/hash-graph-types/entity"; import type { BaseUrl } from "@local/hash-graph-types/ontology"; @@ -595,25 +593,6 @@ export const updateEntity = async ( return updatedEntity; }; -export const validateEntity: ImpureGraphFunction< - { - components: ValidateEntityParamsComponents; - entityTypes: VersionedUrl[]; - properties: PropertyObjectWithMetadata; - }, - Promise -> = async (context, authentication, params) => { - const { components, entityTypes, properties } = params; - - return await context.graphApi - .validateEntity(authentication.actorId, { - components, - entityTypes, - properties, - }) - .then(({ data }) => data); -}; - /** * Get the incoming links of an entity. * diff --git a/apps/hash-api/src/graphql/resolvers/knowledge/entity/entity.ts b/apps/hash-api/src/graphql/resolvers/knowledge/entity/entity.ts index d63a845723f..c0936caa622 100644 --- a/apps/hash-api/src/graphql/resolvers/knowledge/entity/entity.ts +++ b/apps/hash-api/src/graphql/resolvers/knowledge/entity/entity.ts @@ -5,7 +5,7 @@ import type { Filter, QueryTemporalAxesUnresolved, } from "@local/hash-graph-client"; -import type { Entity } from "@local/hash-graph-sdk/entity"; +import { Entity } from "@local/hash-graph-sdk/entity"; import type { AccountGroupId, AccountId, @@ -39,7 +39,6 @@ import { removeEntityAdministrator, removeEntityEditor, updateEntity, - validateEntity, } from "../../../../graph/knowledge/primitive/entity"; import { createLinkEntity, @@ -404,7 +403,7 @@ export const updateEntitiesResolver: ResolverFn< }; export const validateEntityResolver: ResolverFn< - Promise, + Promise, Record, LoggedInGraphQLContext, QueryValidateEntityArgs @@ -412,9 +411,10 @@ export const validateEntityResolver: ResolverFn< const { authentication } = graphQLContext; const context = graphQLContextToImpureGraphContext(graphQLContext); - await validateEntity(context, authentication, params); - - return true; + return ( + (await Entity.validate(context.graphApi, authentication, params)) === + undefined + ); }; export const archiveEntityResolver: ResolverFn< diff --git a/libs/@blockprotocol/type-system/rust/src/schema/entity_type/closed.rs b/libs/@blockprotocol/type-system/rust/src/schema/entity_type/closed.rs index 433a167e967..35487ce68d0 100644 --- a/libs/@blockprotocol/type-system/rust/src/schema/entity_type/closed.rs +++ b/libs/@blockprotocol/type-system/rust/src/schema/entity_type/closed.rs @@ -35,6 +35,16 @@ pub struct ClosedEntityTypeMetadata { pub inverse: InverseEntityTypeMetadata, } +impl ClosedEntityTypeMetadata { + #[must_use] + pub fn is_link(&self) -> bool { + self.all_of.iter().any(|entity_type| { + entity_type.id.base_url.as_str() + == "https://blockprotocol.org/@blockprotocol/types/entity-type/link/" + }) + } +} + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[cfg_attr(target_arch = "wasm32", derive(tsify::Tsify))] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -160,6 +170,10 @@ impl ClosedMultiEntityType { } } + pub fn is_link(&self) -> bool { + self.all_of.iter().any(ClosedEntityTypeMetadata::is_link) + } + /// Creates a closed entity type from multiple closed entity types. /// /// This results in a closed entity type which is used for entities with multiple types. diff --git a/libs/@blockprotocol/type-system/rust/src/schema/entity_type/mod.rs b/libs/@blockprotocol/type-system/rust/src/schema/entity_type/mod.rs index b702f5883cf..6a66b062449 100644 --- a/libs/@blockprotocol/type-system/rust/src/schema/entity_type/mod.rs +++ b/libs/@blockprotocol/type-system/rust/src/schema/entity_type/mod.rs @@ -1,6 +1,7 @@ pub use self::{ closed::{ ClosedEntityType, ClosedEntityTypeMetadata, ClosedMultiEntityType, EntityTypeResolveData, + ResolveClosedEntityTypeError, }, constraints::EntityConstraints, reference::EntityTypeReference, @@ -81,12 +82,6 @@ pub struct EntityTypeDisplayMetadata { pub icon: Option, } -impl EntityTypeDisplayMetadata { - pub const fn is_empty(&self) -> bool { - self.label_property.is_none() && self.icon.is_none() - } -} - #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(target_arch = "wasm32", derive(tsify::Tsify))] #[serde(rename_all = "camelCase", deny_unknown_fields)] diff --git a/libs/@blockprotocol/type-system/rust/src/schema/mod.rs b/libs/@blockprotocol/type-system/rust/src/schema/mod.rs index ef5f1d8d29d..2a8197d1a43 100644 --- a/libs/@blockprotocol/type-system/rust/src/schema/mod.rs +++ b/libs/@blockprotocol/type-system/rust/src/schema/mod.rs @@ -38,6 +38,7 @@ pub use self::{ EntityType, EntityTypeReference, EntityTypeResolveData, EntityTypeSchemaMetadata, EntityTypeToEntityTypeEdge, EntityTypeToPropertyTypeEdge, EntityTypeValidationError, EntityTypeValidator, InverseEntityTypeMetadata, PartialEntityType, + ResolveClosedEntityTypeError, }, identifier::{DataTypeUuid, EntityTypeUuid, OntologyTypeUuid, PropertyTypeUuid}, object::{ diff --git a/libs/@local/graph/api/Cargo.toml b/libs/@local/graph/api/Cargo.toml index 8cf9f625f1f..7fd0c3d7b00 100644 --- a/libs/@local/graph/api/Cargo.toml +++ b/libs/@local/graph/api/Cargo.toml @@ -36,6 +36,7 @@ harpc-types = { workspace = true } hash-graph-store = { workspace = true, features = ["utoipa"] } hash-graph-temporal-versioning = { workspace = true } hash-graph-type-defs = { workspace = true } +hash-graph-validation = { workspace = true } hash-status = { workspace = true } type-system = { workspace = true, features = ["utoipa"] } diff --git a/libs/@local/graph/api/openapi/models/multi_report.json b/libs/@local/graph/api/openapi/models/multi_report.json new file mode 100644 index 00000000000..72c5757cb28 --- /dev/null +++ b/libs/@local/graph/api/openapi/models/multi_report.json @@ -0,0 +1,9 @@ +{ + "title": "MultiReport", + "type": "array", + "description": "An error-stack Report object which may contain multiple errors", + "items": { + "$ref": "./report_context_info.json" + }, + "minItems": 1 +} diff --git a/libs/@local/graph/api/openapi/models/report.json b/libs/@local/graph/api/openapi/models/report.json new file mode 100644 index 00000000000..bee8ba0b57a --- /dev/null +++ b/libs/@local/graph/api/openapi/models/report.json @@ -0,0 +1,10 @@ +{ + "title": "Report", + "type": "array", + "description": "An error-stack Report object which may contains exactly one error", + "items": { + "$ref": "./report_context_info.json" + }, + "minItems": 1, + "maxItems": 1 +} diff --git a/libs/@local/graph/api/openapi/models/report_context_info.json b/libs/@local/graph/api/openapi/models/report_context_info.json new file mode 100644 index 00000000000..590c9567976 --- /dev/null +++ b/libs/@local/graph/api/openapi/models/report_context_info.json @@ -0,0 +1,23 @@ +{ + "title": "ReportContextInfo", + "type": "object", + "properties": { + "context": { + "type": "string", + "description": "The user-facing message of the status." + }, + "attachments": { + "type": "array", + "items": { + "type": "string" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "./report_context_info.json" + } + } + }, + "required": ["context", "attachments", "sources"] +} diff --git a/libs/@local/graph/api/openapi/openapi.json b/libs/@local/graph/api/openapi/openapi.json index 9cb5652e3ce..4be6c0bfe65 100644 --- a/libs/@local/graph/api/openapi/openapi.json +++ b/libs/@local/graph/api/openapi/openapi.json @@ -1318,8 +1318,18 @@ "required": true }, "responses": { - "204": { - "description": "The validation passed" + "200": { + "description": "The validation report", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/EntityValidationReport" + } + } + } + } }, "400": { "description": "The entity validation failed" @@ -4772,10 +4782,85 @@ } } }, + "EntityTypesError": { + "oneOf": [ + { + "type": "object", + "required": [ + "type", + "error" + ], + "properties": { + "error": { + "$ref": "#/components/schemas/Report" + }, + "type": { + "type": "string", + "enum": [ + "empty" + ] + } + } + }, + { + "type": "object", + "required": [ + "type", + "error" + ], + "properties": { + "error": { + "$ref": "#/components/schemas/Report" + }, + "type": { + "type": "string", + "enum": [ + "entityTypeRetrieval" + ] + } + } + }, + { + "type": "object", + "required": [ + "type", + "error" + ], + "properties": { + "error": { + "$ref": "#/components/schemas/Report" + }, + "type": { + "type": "string", + "enum": [ + "resolveClosedEntityType" + ] + } + } + } + ], + "discriminator": { + "propertyName": "type" + } + }, "EntityUuid": { "type": "string", "format": "uuid" }, + "EntityValidationReport": { + "type": "object", + "properties": { + "link": { + "$ref": "#/components/schemas/LinkValidationReport" + }, + "metadata": { + "$ref": "#/components/schemas/MetadataValidationReport" + }, + "properties": { + "$ref": "#/components/schemas/PropertyValidationReport" + } + } + }, "EntityValidationType": { "type": "array", "items": { @@ -6218,6 +6303,217 @@ }, "additionalProperties": false }, + "LinkDataStateError": { + "oneOf": [ + { + "type": "object", + "required": [ + "type", + "error" + ], + "properties": { + "error": { + "$ref": "#/components/schemas/Report" + }, + "type": { + "type": "string", + "enum": [ + "missing" + ] + } + } + }, + { + "type": "object", + "required": [ + "type", + "error" + ], + "properties": { + "error": { + "$ref": "#/components/schemas/Report" + }, + "type": { + "type": "string", + "enum": [ + "unexpected" + ] + } + } + } + ], + "discriminator": { + "propertyName": "type" + } + }, + "LinkDataValidationReport": { + "type": "object", + "properties": { + "leftEntity": { + "allOf": [ + { + "$ref": "#/components/schemas/LinkedEntityError" + } + ], + "nullable": true + }, + "linkType": { + "allOf": [ + { + "$ref": "#/components/schemas/LinkError" + } + ], + "nullable": true + }, + "rightEntity": { + "allOf": [ + { + "$ref": "#/components/schemas/LinkedEntityError" + } + ], + "nullable": true + }, + "targetType": { + "allOf": [ + { + "$ref": "#/components/schemas/LinkTargetError" + } + ], + "nullable": true + } + } + }, + "LinkError": { + "oneOf": [ + { + "type": "object", + "required": [ + "data", + "type" + ], + "properties": { + "data": { + "$ref": "#/components/schemas/UnexpectedEntityType" + }, + "type": { + "type": "string", + "enum": [ + "unexpectedEntityType" + ] + } + } + } + ], + "discriminator": { + "propertyName": "type" + } + }, + "LinkTargetError": { + "oneOf": [ + { + "type": "object", + "required": [ + "data", + "type" + ], + "properties": { + "data": { + "$ref": "#/components/schemas/UnexpectedEntityType" + }, + "type": { + "type": "string", + "enum": [ + "unexpectedEntityType" + ] + } + } + } + ], + "discriminator": { + "propertyName": "type" + } + }, + "LinkValidationReport": { + "allOf": [ + { + "$ref": "#/components/schemas/LinkDataValidationReport" + }, + { + "type": "object", + "properties": { + "linkData": { + "allOf": [ + { + "$ref": "#/components/schemas/LinkDataStateError" + } + ], + "nullable": true + } + } + } + ] + }, + "LinkedEntityError": { + "oneOf": [ + { + "type": "object", + "required": [ + "type", + "error" + ], + "properties": { + "error": { + "$ref": "#/components/schemas/Report" + }, + "type": { + "type": "string", + "enum": [ + "entityRetrieval" + ] + } + } + }, + { + "type": "object", + "required": [ + "type", + "error" + ], + "properties": { + "error": { + "$ref": "#/components/schemas/Report" + }, + "type": { + "type": "string", + "enum": [ + "entityTypeRetrieval" + ] + } + } + }, + { + "type": "object", + "required": [ + "type", + "error" + ], + "properties": { + "error": { + "$ref": "#/components/schemas/Report" + }, + "type": { + "type": "string", + "enum": [ + "resolveClosedEntityType" + ] + } + } + } + ], + "discriminator": { + "propertyName": "type" + } + }, "LoadExternalDataTypeRequest": { "oneOf": [ { @@ -6392,6 +6688,22 @@ } ] }, + "MetadataValidationReport": { + "type": "object", + "properties": { + "entityTypes": { + "allOf": [ + { + "$ref": "#/components/schemas/EntityTypesError" + } + ], + "nullable": true + }, + "properties": { + "$ref": "#/components/schemas/PropertyMetadataValidationReport" + } + } + }, "ModifyDataTypeAuthorizationRelationship": { "type": "object", "required": [ @@ -6496,6 +6808,9 @@ } } }, + "MultiReport": { + "$ref": "./models/multi_report.json" + }, "NullOrdering": { "type": "string", "enum": [ @@ -7267,6 +7582,10 @@ }, "additionalProperties": false }, + "PropertyMetadataValidationReport": { + "default": null, + "nullable": true + }, "PropertyObject": { "type": "object", "additionalProperties": { @@ -7690,6 +8009,19 @@ } } }, + "PropertyValidationReport": { + "type": "object", + "properties": { + "error": { + "allOf": [ + { + "$ref": "#/components/schemas/Report" + } + ], + "nullable": true + } + } + }, "PropertyWithMetadata": { "oneOf": [ { @@ -7995,6 +8327,9 @@ ], "description": "Defines the two possible combinations of pinned/variable temporal axes that are used in queries\nthat return [`Subgraph`]s.\n\nThe [`VariableTemporalAxisUnresolved`] is optionally bounded, in the absence of provided\nbounds an inclusive bound at the timestamp at point of resolving is assumed.\n\n[`Subgraph`]: crate::subgraph::Subgraph" }, + "Report": { + "$ref": "./models/report.json" + }, "RightBoundedTemporalInterval": { "type": "object", "required": [ @@ -8249,6 +8584,27 @@ }, "additionalProperties": false }, + "UnexpectedEntityType": { + "type": "object", + "required": [ + "actual", + "expected" + ], + "properties": { + "actual": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VersionedUrl" + } + }, + "expected": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VersionedUrl" + } + } + } + }, "UnresolvedRightBoundedTemporalInterval": { "type": "object", "required": [ diff --git a/libs/@local/graph/api/package.json b/libs/@local/graph/api/package.json index 99af6b8fa02..a286d671d40 100644 --- a/libs/@local/graph/api/package.json +++ b/libs/@local/graph/api/package.json @@ -23,6 +23,7 @@ "@rust/hash-graph-type-defs": "0.0.0-private", "@rust/hash-graph-type-fetcher": "0.0.0-private", "@rust/hash-graph-types": "0.0.0-private", + "@rust/hash-graph-validation": "0.0.0-private", "@rust/hash-status": "0.0.0-private", "@rust/hash-temporal-client": "0.0.0-private" } diff --git a/libs/@local/graph/api/src/rest/entity.rs b/libs/@local/graph/api/src/rest/entity.rs index a4e40b5efcd..fbcb4155a86 100644 --- a/libs/@local/graph/api/src/rest/entity.rs +++ b/libs/@local/graph/api/src/rest/entity.rs @@ -28,9 +28,12 @@ use hash_graph_store::{ ClosedMultiEntityTypeMap, CountEntitiesParams, CreateEntityRequest, DiffEntityParams, DiffEntityResult, EntityQueryCursor, EntityQueryPath, EntityQuerySorting, EntityQuerySortingRecord, EntityQuerySortingToken, EntityQueryToken, EntityStore as _, - EntityValidationType, GetEntitiesParams, GetEntitiesResponse, GetEntitySubgraphParams, - PatchEntityParams, QueryConversion, UpdateEntityEmbeddingsParams, ValidateEntityComponents, - ValidateEntityParams, + EntityTypesError, EntityValidationReport, EntityValidationType, GetEntitiesParams, + GetEntitiesResponse, GetEntitySubgraphParams, LinkDataStateError, LinkDataValidationReport, + LinkError, LinkTargetError, LinkValidationReport, LinkedEntityError, + MetadataValidationReport, PatchEntityParams, PropertyMetadataValidationReport, + PropertyValidationReport, QueryConversion, UnexpectedEntityType, + UpdateEntityEmbeddingsParams, ValidateEntityComponents, ValidateEntityParams, }, entity_type::{EntityTypeResolveDefinitions, IncludeEntityTypeOption}, filter::Filter, @@ -161,6 +164,18 @@ use crate::rest::{ EntityTemporalMetadata, EntityQueryToken, LinkData, + EntityValidationReport, + LinkedEntityError, + LinkDataValidationReport, + LinkDataStateError, + LinkValidationReport, + LinkError, + LinkTargetError, + UnexpectedEntityType, + MetadataValidationReport, + EntityTypesError, + PropertyMetadataValidationReport, + PropertyValidationReport, DiffEntityParams, DiffEntityResult, @@ -339,7 +354,7 @@ where ("X-Authenticated-User-Actor-Id" = AccountId, Header, description = "The ID of the actor which is used to authorize the request"), ), responses( - (status = 204, description = "The validation passed"), + (status = 200, content_type = "application/json", description = "The validation report", body = HashMap), (status = 400, content_type = "application/json", description = "The entity validation failed"), (status = 404, description = "Entity Type URL was not found"), @@ -356,7 +371,7 @@ async fn validate_entity( authorization_api_pool: Extension>, temporal_client: Extension>>, Json(body): Json, -) -> Result +) -> Result>, Response> where S: StorePool + Send + Sync, A: AuthorizationApiPool + Send + Sync, @@ -375,13 +390,11 @@ where .await .map_err(report_to_response)?; - store - .validate_entity(actor_id, Consistency::FullyConsistent, params) - .await - .attach(hash_status::StatusCode::InvalidArgument) - .map_err(report_to_response)?; - - Ok(StatusCode::NO_CONTENT) + Ok(Json( + store + .validate_entity(actor_id, Consistency::FullyConsistent, params) + .await, + )) } #[utoipa::path( diff --git a/libs/@local/graph/api/src/rest/json_schemas/multi_report.json b/libs/@local/graph/api/src/rest/json_schemas/multi_report.json new file mode 100644 index 00000000000..72c5757cb28 --- /dev/null +++ b/libs/@local/graph/api/src/rest/json_schemas/multi_report.json @@ -0,0 +1,9 @@ +{ + "title": "MultiReport", + "type": "array", + "description": "An error-stack Report object which may contain multiple errors", + "items": { + "$ref": "./report_context_info.json" + }, + "minItems": 1 +} diff --git a/libs/@local/graph/api/src/rest/json_schemas/report.json b/libs/@local/graph/api/src/rest/json_schemas/report.json new file mode 100644 index 00000000000..bee8ba0b57a --- /dev/null +++ b/libs/@local/graph/api/src/rest/json_schemas/report.json @@ -0,0 +1,10 @@ +{ + "title": "Report", + "type": "array", + "description": "An error-stack Report object which may contains exactly one error", + "items": { + "$ref": "./report_context_info.json" + }, + "minItems": 1, + "maxItems": 1 +} diff --git a/libs/@local/graph/api/src/rest/json_schemas/report_context_info.json b/libs/@local/graph/api/src/rest/json_schemas/report_context_info.json new file mode 100644 index 00000000000..590c9567976 --- /dev/null +++ b/libs/@local/graph/api/src/rest/json_schemas/report_context_info.json @@ -0,0 +1,23 @@ +{ + "title": "ReportContextInfo", + "type": "object", + "properties": { + "context": { + "type": "string", + "description": "The user-facing message of the status." + }, + "attachments": { + "type": "array", + "items": { + "type": "string" + } + }, + "sources": { + "type": "array", + "items": { + "$ref": "./report_context_info.json" + } + } + }, + "required": ["context", "attachments", "sources"] +} diff --git a/libs/@local/graph/api/src/rest/mod.rs b/libs/@local/graph/api/src/rest/mod.rs index c200560998f..8d18b3f761a 100644 --- a/libs/@local/graph/api/src/rest/mod.rs +++ b/libs/@local/graph/api/src/rest/mod.rs @@ -457,6 +457,8 @@ impl Modify for ExternalRefAddon { ("PartialEntityType", "partial_entity_type"), ("ClosedMultiEntityType", "closed_multi_entity_type"), ("Status", "status"), + ("Report", "report"), + ("MultiReport", "multi_report"), ] { *components.schemas.entry(name.to_owned()).or_default() = Ref::new(format!("./models/{model}.json")).into(); diff --git a/libs/@local/graph/api/src/rest/status.rs b/libs/@local/graph/api/src/rest/status.rs index 1f6fccfcb71..1902878df63 100644 --- a/libs/@local/graph/api/src/rest/status.rs +++ b/libs/@local/graph/api/src/rest/status.rs @@ -1,4 +1,5 @@ -use core::{error::Error, fmt::Debug}; +use core::{error::Error, fmt::Debug, mem}; +use std::collections::HashMap; use axum::{ Json, @@ -7,6 +8,7 @@ use axum::{ use error_stack::Report; use hash_graph_authorization::backend::PermissionAssertion; use hash_graph_postgres_store::store::error::BaseUrlAlreadyExists; +use hash_graph_store::entity::EntityValidationReport; use hash_status::{Status, StatusCode}; use serde::Serialize; @@ -27,11 +29,18 @@ where response } +#[derive(Debug, Serialize)] +#[serde(bound = "C: Error + Send + Sync + 'static")] +struct ValidationContent { + validation: HashMap, + report: Report<[C]>, +} + pub(crate) fn report_to_response(report: impl Into>) -> Response where C: Error + Send + Sync + 'static, { - let report = report.into(); + let mut report = report.into(); let status_code = report .request_ref::() .next() @@ -50,9 +59,24 @@ where // TODO: Currently, this mostly duplicates the error printed below, when more information is // added to the `Report` event consider commenting in this line again. // hash_tracing::sentry::capture_report(&report); - tracing::error!(error = ?report, tags.code = ?status_code.to_http_code()); - status_to_response(Status::new(status_code, Some(report.to_string()), vec![ - report, - ])) + let message = report.to_string(); + if let Some(validation) = report + .downcast_mut::>() + .map(mem::take) + { + tracing::error!(error = ?report, ?validation, tags.code = ?status_code.to_http_code()); + let status_code = if !validation.is_empty() && status_code == StatusCode::Unknown { + StatusCode::InvalidArgument + } else { + status_code + }; + + status_to_response(Status::new(status_code, Some(message), vec![ + ValidationContent { validation, report }, + ])) + } else { + tracing::error!(error = ?report, tags.code = ?status_code.to_http_code()); + status_to_response(Status::new(status_code, Some(message), vec![report])) + } } diff --git a/libs/@local/graph/postgres-store/src/snapshot/entity/batch.rs b/libs/@local/graph/postgres-store/src/snapshot/entity/batch.rs index 80984266b27..f4e6048d572 100644 --- a/libs/@local/graph/postgres-store/src/snapshot/entity/batch.rs +++ b/libs/@local/graph/postgres-store/src/snapshot/entity/batch.rs @@ -1,10 +1,15 @@ -use error_stack::{Report, ResultExt as _}; +use std::collections::HashMap; + +use error_stack::{Report, ResultExt as _, ensure}; use futures::{StreamExt as _, TryStreamExt as _, stream}; use hash_graph_authorization::{ AuthorizationApi, backend::ZanzibarBackend, schema::EntityRelationAndSubject, }; use hash_graph_store::{ - entity::{EntityStore as _, ValidateEntityComponents}, + entity::{ + EntityStore as _, EntityValidationReport, PropertyValidationReport, + ValidateEntityComponents, + }, error::InsertionError, filter::Filter, query::Read, @@ -305,7 +310,8 @@ where let mut properties_updates = Vec::new(); let mut metadata_updates = Vec::new(); - for mut entity in entities { + let mut validation_reports = HashMap::::new(); + for (index, mut entity) in entities.into_iter().enumerate() { let validation_components = if entity.metadata.record_id.entity_id.draft_id.is_some() { ValidateEntityComponents::draft() } else { @@ -333,16 +339,21 @@ where ) .change_context(InsertionError)?; - EntityPreprocessor { + let mut preprocessor = EntityPreprocessor { components: validation_components, + }; + + if let Err(error) = preprocessor + .visit_object( + &entity_type, + &mut property_with_metadata, + &validator_provider, + ) + .await + { + validation_reports.entry(index).or_default().properties = + PropertyValidationReport { error: Some(error) }; } - .visit_object( - &entity_type, - &mut property_with_metadata, - &validator_provider, - ) - .await - .change_context(InsertionError)?; let (properties, metadata) = property_with_metadata.into_parts(); let mut changed = false; @@ -364,10 +375,14 @@ where }; validation_components.link_validation = postgres_client.settings.validate_links; - entity + let validation_report = entity .validate(&entity_type, validation_components, &validator_provider) - .await - .change_context(InsertionError)?; + .await; + if !validation_report.is_valid() { + let validation = validation_reports.entry(index).or_default(); + validation.link = validation_report.link; + validation.metadata.properties = validation_report.property_metadata; + } if changed { edition_ids_updates.push(entity.metadata.record_id.edition_id); @@ -376,6 +391,11 @@ where } } + ensure!( + validation_reports.is_empty(), + Report::new(InsertionError).attach(validation_reports) + ); + postgres_client .as_client() .client() diff --git a/libs/@local/graph/postgres-store/src/store/postgres/knowledge/entity/mod.rs b/libs/@local/graph/postgres-store/src/store/postgres/knowledge/entity/mod.rs index a9daf51ca66..8ed1b01ad13 100644 --- a/libs/@local/graph/postgres-store/src/store/postgres/knowledge/entity/mod.rs +++ b/libs/@local/graph/postgres-store/src/store/postgres/knowledge/entity/mod.rs @@ -4,7 +4,7 @@ use alloc::{borrow::Cow, collections::BTreeSet}; use core::{borrow::Borrow as _, iter::once, mem}; use std::collections::{HashMap, HashSet}; -use error_stack::{Report, ReportSink, ResultExt as _, bail}; +use error_stack::{FutureExt as _, Report, ResultExt as _, TryReportStreamExt as _, bail, ensure}; use futures::{StreamExt as _, TryStreamExt as _, stream}; use hash_graph_authorization::{ AuthorizationApi, @@ -17,11 +17,12 @@ use hash_graph_authorization::{ }; use hash_graph_store::{ entity::{ - ClosedMultiEntityTypeMap, CountEntitiesParams, CreateEntityParams, EntityQueryPath, - EntityQuerySorting, EntityStore, EntityValidationType, GetEntitiesParams, - GetEntitiesResponse, GetEntitySubgraphParams, GetEntitySubgraphResponse, PatchEntityParams, - QueryConversion, UpdateEntityEmbeddingsParams, ValidateEntityComponents, - ValidateEntityError, ValidateEntityParams, + ClosedMultiEntityTypeMap, CountEntitiesParams, CreateEntityParams, EmptyEntityTypes, + EntityQueryPath, EntityQuerySorting, EntityStore, EntityTypeRetrieval, EntityTypesError, + EntityValidationReport, EntityValidationType, GetEntitiesParams, GetEntitiesResponse, + GetEntitySubgraphParams, GetEntitySubgraphResponse, PatchEntityParams, + PropertyValidationReport, QueryConversion, UpdateEntityEmbeddingsParams, + ValidateEntityComponents, ValidateEntityParams, }, entity_type::IncludeEntityTypeOption, error::{InsertionError, QueryError, UpdateError}, @@ -62,7 +63,7 @@ use hash_graph_types::{ ontology::{DataTypeLookup, OntologyTypeProvider}, owned_by_id::OwnedById, }; -use hash_graph_validation::{EntityPreprocessor, EntityValidationError, Validate as _}; +use hash_graph_validation::{EntityPreprocessor, Validate as _}; use hash_status::StatusCode; use postgres_types::ToSql; use serde_json::Value as JsonValue; @@ -773,7 +774,8 @@ where authorization: Some((actor_id, Consistency::FullyConsistent)), }; - for mut params in params { + let mut validation_reports = HashMap::::new(); + for (index, mut params) in params.into_iter().enumerate() { let entity_type = ClosedMultiEntityType::from_multi_type_closed_schema( stream::iter(¶ms.entity_type_ids) .then(|entity_type_url| async { @@ -790,19 +792,22 @@ where ) .change_context(InsertionError)?; - let mut validation_components = if params.draft { - ValidateEntityComponents::draft() - } else { - ValidateEntityComponents::full() + let mut preprocessor = EntityPreprocessor { + components: if params.draft { + ValidateEntityComponents::draft() + } else { + ValidateEntityComponents::full() + }, }; - validation_components.link_validation = self.settings.validate_links; - EntityPreprocessor { - components: validation_components, + preprocessor.components.link_validation = self.settings.validate_links; + + if let Err(error) = preprocessor + .visit_object(&entity_type, &mut params.properties, &validator_provider) + .await + { + validation_reports.entry(index).or_default().properties = + PropertyValidationReport { error: Some(error) }; } - .visit_object(&entity_type, &mut params.properties, &validator_provider) - .await - .attach(StatusCode::InvalidArgument) - .change_context(InsertionError)?; let (properties, property_metadata) = params.properties.into_parts(); @@ -930,7 +935,7 @@ where }, }); - validation_params.push((entity_type, validation_components)); + validation_params.push((entity_type, preprocessor.components)); let current_num_relationships = relationships.len(); relationships.extend( @@ -1093,13 +1098,24 @@ where authorization: Some((actor_id, Consistency::FullyConsistent)), }; - for (entity, (schema, components)) in entities.iter().zip(validation_params) { - entity + for (index, (entity, (schema, components))) in + entities.iter().zip(validation_params).enumerate() + { + let validation_report = entity .validate(&schema, components, &validator_provider) - .await - .change_context(InsertionError)?; + .await; + if !validation_report.is_valid() { + let report = validation_reports.entry(index).or_default(); + report.link = validation_report.link; + report.metadata.properties = validation_report.property_metadata; + } } + ensure!( + validation_reports.is_empty(), + Report::new(InsertionError).attach(validation_reports) + ); + let commit_result = transaction.commit().await.change_context(InsertionError); if let Err(error) = commit_result { let mut error = error.expand(); @@ -1144,8 +1160,8 @@ where actor_id: AccountId, consistency: Consistency<'_>, params: Vec>, - ) -> Result<(), Report> { - let mut status = ReportSink::new(); + ) -> HashMap { + let mut validation_reports = HashMap::::new(); let validator_provider = StoreProvider { store: self, @@ -1153,61 +1169,76 @@ where authorization: Some((actor_id, Consistency::FullyConsistent)), }; - for mut params in params { + for (index, mut params) in params.into_iter().enumerate() { + let mut validation_report = EntityValidationReport::default(); + let schema = match params.entity_types { EntityValidationType::ClosedSchema(schema) => schema, - EntityValidationType::Id(entity_type_urls) => Cow::Owned( - ClosedMultiEntityType::from_multi_type_closed_schema( - stream::iter(entity_type_urls.as_ref()) - .then(|entity_type_url| async { - OntologyTypeProvider::::provide_type( - &validator_provider, - entity_type_url, - ) - .await - .map(|entity_type| (*entity_type).clone()) + EntityValidationType::Id(entity_type_urls) => { + let entity_type = stream::iter(entity_type_urls.as_ref()) + .then(|entity_type_url| { + OntologyTypeProvider::::provide_type( + &validator_provider, + entity_type_url, + ) + .change_context_lazy(|| { + EntityTypeRetrieval { + entity_type_url: entity_type_url.clone(), + } }) - .try_collect::>() - .await - .change_context(ValidateEntityError)?, - ) - .change_context(ValidateEntityError)?, - ), + }) + .map_ok(|entity_type| (*entity_type).clone()) + .try_collect_reports::>() + .await + .map_err(EntityTypesError::EntityTypeRetrieval) + .and_then(|entity_types| { + ClosedMultiEntityType::from_multi_type_closed_schema(entity_types) + .map_err(EntityTypesError::ResolveClosedEntityType) + }); + match entity_type { + Ok(entity_type) => Cow::Owned(entity_type), + Err(error) => { + validation_report.metadata.entity_types = Some(error); + validation_reports.insert(index, validation_report); + continue; + } + } + } }; if schema.all_of.is_empty() { - let error = Report::new(EntityValidationError::EmptyEntityTypes); - status.append(error); + validation_report.metadata.entity_types = + Some(EntityTypesError::Empty(Report::new(EmptyEntityTypes))); }; - let pre_process_result = EntityPreprocessor { + let mut preprocessor = EntityPreprocessor { components: params.components, - } - .visit_object( - schema.as_ref(), - params.properties.to_mut(), - &validator_provider, - ) - .await - .change_context(EntityValidationError::InvalidProperties); - if let Err(error) = pre_process_result { - status.append(error); + }; + + if let Err(error) = preprocessor + .visit_object( + schema.as_ref(), + params.properties.to_mut(), + &validator_provider, + ) + .await + { + validation_reports.entry(index).or_default().properties = + PropertyValidationReport { error: Some(error) }; } - if let Err(error) = params + validation_report.link = params .link_data .as_deref() .validate(&schema, params.components, &validator_provider) - .await - { - status.append(error); + .await; + + if !validation_report.is_valid() { + validation_reports.insert(index, validation_report); } } - status - .finish() - .change_context(ValidateEntityError) - .attach(StatusCode::InvalidArgument) + validation_reports } #[tracing::instrument(level = "info", skip(self, params))] @@ -1704,15 +1735,19 @@ where }; validation_components.link_validation = transaction.settings.validate_links; + let mut validation_report = EntityValidationReport::default(); let (properties, property_metadata) = if let PropertyWithMetadata::Object(mut object) = properties_with_metadata { - EntityPreprocessor { + let mut preprocessor = EntityPreprocessor { components: validation_components, + }; + if let Err(error) = preprocessor + .visit_object(&entity_type, &mut object, &validator_provider) + .await + { + validation_report.properties = PropertyValidationReport { error: Some(error) }; } - .visit_object(&entity_type, &mut object, &validator_provider) - .await - .attach(StatusCode::InvalidArgument) - .change_context(UpdateError)?; + let (properties, property_metadata) = object.into_parts(); (properties, property_metadata) } else { @@ -1897,10 +1932,19 @@ where cache: store_cache, authorization: Some((actor_id, Consistency::FullyConsistent)), }; - entities[0] + let post_validation_report = entities[0] .validate(&entity_type, validation_components, &validator_provider) - .await - .change_context(UpdateError)?; + .await; + validation_report.link = post_validation_report.link; + validation_report.metadata.properties = post_validation_report.property_metadata; + + ensure!( + validation_report.is_valid(), + Report::new(UpdateError).attach(HashMap::from([( + entities[0].metadata.record_id.entity_id, + validation_report + )])) + ); transaction.commit().await.change_context(UpdateError)?; diff --git a/libs/@local/graph/postgres-store/src/store/validation.rs b/libs/@local/graph/postgres-store/src/store/validation.rs index 3f45bab6bf0..93a5abfb765 100644 --- a/libs/@local/graph/postgres-store/src/store/validation.rs +++ b/libs/@local/graph/postgres-store/src/store/validation.rs @@ -22,8 +22,8 @@ use hash_graph_types::{ account::AccountId, knowledge::entity::{Entity, EntityId}, ontology::{ - DataTypeLookup, DataTypeWithMetadata, EntityTypeProvider, EntityTypeWithMetadata, - OntologyTypeProvider, PropertyTypeProvider, PropertyTypeWithMetadata, + DataTypeLookup, DataTypeWithMetadata, EntityTypeWithMetadata, OntologyTypeProvider, + PropertyTypeWithMetadata, }, }; use hash_graph_validation::EntityProvider; @@ -407,13 +407,6 @@ where } } -impl PropertyTypeProvider for StoreProvider<'_, PostgresStore> -where - C: AsClient, - A: AuthorizationApi, -{ -} - impl StoreProvider<'_, PostgresStore> where C: AsClient, @@ -518,71 +511,6 @@ where } } -impl EntityTypeProvider for StoreProvider<'_, PostgresStore> -where - C: AsClient, - A: AuthorizationApi, -{ - #[expect(refining_impl_trait)] - async fn is_super_type_of( - &self, - parent: &VersionedUrl, - child: &VersionedUrl, - ) -> Result> { - let client = self.store.as_client().client(); - let child_id = EntityTypeUuid::from_url(child); - let parent_id = EntityTypeUuid::from_url(parent); - - Ok(client - .query_one( - " - SELECT EXISTS ( - SELECT 1 FROM entity_type_inherits_from - WHERE source_entity_type_ontology_id = $1 - AND target_entity_type_ontology_id = $2 - ); - ", - &[&child_id, &parent_id], - ) - .await - .change_context(QueryError)? - .get(0)) - } - - #[expect(refining_impl_trait)] - async fn find_parents( - &self, - entity_types: &[VersionedUrl], - ) -> Result, Report> { - let entity_type_ids = entity_types - .iter() - .map(EntityTypeUuid::from_url) - .collect::>(); - - Ok(self - .store - .as_client() - .query( - " - SELECT base_url, version - FROM entity_type_inherits_from - JOIN ontology_ids ON target_entity_type_ontology_id = ontology_id - WHERE source_entity_type_ontology_id = ANY($1) - ORDER BY depth ASC; - ", - &[&entity_type_ids], - ) - .await - .change_context(QueryError)? - .into_iter() - .map(|row| VersionedUrl { - base_url: row.get(0), - version: row.get(1), - }) - .collect()) - } -} - impl EntityProvider for StoreProvider<'_, PostgresStore> where C: AsClient, diff --git a/libs/@local/graph/sdk/typescript/src/entity.ts b/libs/@local/graph/sdk/typescript/src/entity.ts index 6b3d4f991a8..dfb00dbb132 100644 --- a/libs/@local/graph/sdk/typescript/src/entity.ts +++ b/libs/@local/graph/sdk/typescript/src/entity.ts @@ -3,6 +3,7 @@ import { typedEntries, typedKeys } from "@local/advanced-types/typed-entries"; import type { CreateEntityRequest as GraphApiCreateEntityRequest, Entity as GraphApiEntity, + EntityValidationReport, GraphApi, OriginProvenance, PatchEntityParams as GraphApiPatchEntityParams, @@ -996,10 +997,10 @@ export class Entity { params: Omit & { properties: PropertyObjectWithMetadata; }, - ): Promise { + ): Promise { return await graphAPI .validateEntity(authentication.actorId, params) - .then(({ data }) => data); + .then(({ data }) => data["0"]); } public async patch( diff --git a/libs/@local/graph/store/Cargo.toml b/libs/@local/graph/store/Cargo.toml index eaed681396e..160f5ac42c9 100644 --- a/libs/@local/graph/store/Cargo.toml +++ b/libs/@local/graph/store/Cargo.toml @@ -8,7 +8,7 @@ authors.workspace = true [dependencies] # Public workspace dependencies -error-stack = { workspace = true } +error-stack = { workspace = true, features = ["serde"] } hash-graph-authorization = { workspace = true, public = true } hash-temporal-client = { workspace = true, public = true } diff --git a/libs/@local/graph/store/src/entity/mod.rs b/libs/@local/graph/store/src/entity/mod.rs index 38dd395e302..5e18edb0389 100644 --- a/libs/@local/graph/store/src/entity/mod.rs +++ b/libs/@local/graph/store/src/entity/mod.rs @@ -12,10 +12,18 @@ pub use self::{ QueryConversion, UpdateEntityEmbeddingsParams, ValidateEntityComponents, ValidateEntityError, ValidateEntityParams, }, + validation_report::{ + EmptyEntityTypes, EntityRetrieval, EntityTypeRetrieval, EntityTypesError, + EntityValidationReport, LinkDataStateError, LinkDataValidationReport, LinkError, + LinkTargetError, LinkValidationReport, LinkedEntityError, MetadataValidationReport, + MissingLinkData, PropertyMetadataValidationReport, PropertyValidationReport, + UnexpectedEntityType, UnexpectedLinkData, + }, }; mod query; mod store; +mod validation_report; use hash_graph_types::knowledge::entity::Entity; diff --git a/libs/@local/graph/store/src/entity/store.rs b/libs/@local/graph/store/src/entity/store.rs index dcdbe73fe80..aa9861d7337 100644 --- a/libs/@local/graph/store/src/entity/store.rs +++ b/libs/@local/graph/store/src/entity/store.rs @@ -26,7 +26,7 @@ use utoipa::{ }; use crate::{ - entity::{EntityQueryCursor, EntityQuerySorting}, + entity::{EntityQueryCursor, EntityQuerySorting, EntityValidationReport}, entity_type::{EntityTypeResolveDefinitions, IncludeEntityTypeOption}, error::{InsertionError, QueryError, UpdateError}, filter::Filter, @@ -383,7 +383,7 @@ pub trait EntityStore { actor_id: AccountId, consistency: Consistency<'_>, params: ValidateEntityParams<'_>, - ) -> impl Future>> + Send { + ) -> impl Future> + Send { self.validate_entities(actor_id, consistency, vec![params]) } @@ -397,7 +397,7 @@ pub trait EntityStore { actor_id: AccountId, consistency: Consistency<'_>, params: Vec>, - ) -> impl Future>> + Send; + ) -> impl Future> + Send; /// Get a list of entities specified by the [`GetEntitiesParams`]. /// diff --git a/libs/@local/graph/store/src/entity/validation_report.rs b/libs/@local/graph/store/src/entity/validation_report.rs new file mode 100644 index 00000000000..3ce34a6a02c --- /dev/null +++ b/libs/@local/graph/store/src/entity/validation_report.rs @@ -0,0 +1,208 @@ +use std::collections::HashSet; + +use error_stack::Report; +use hash_graph_types::knowledge::{entity::EntityId, property::visitor::TraversalError}; +use type_system::{schema::ResolveClosedEntityTypeError, url::VersionedUrl}; + +#[derive(Debug, derive_more::Display, derive_more::Error)] +#[display("Could not read the entity")] +#[must_use] +pub struct EntityRetrieval { + pub entity_id: EntityId, +} + +#[derive(Debug, derive_more::Display, derive_more::Error)] +#[display("Could not read the entity type {entity_type_url}")] +#[must_use] +pub struct EntityTypeRetrieval { + pub entity_type_url: VersionedUrl, +} + +#[derive(Debug, serde::Serialize)] +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[serde(tag = "type", content = "error", rename_all = "camelCase")] +#[must_use] +pub enum LinkedEntityError { + EntityRetrieval(Report), + EntityTypeRetrieval( + #[cfg_attr(feature = "utoipa", schema(value_type = MultiReport))] + Report<[EntityTypeRetrieval]>, + ), + ResolveClosedEntityType(Report), +} + +#[derive(Debug, derive_more::Display, derive_more::Error)] +#[display("The entity is a link but does not contain link data")] +#[must_use] +pub struct MissingLinkData; + +#[derive(Debug, derive_more::Display, derive_more::Error)] +#[display("The entity is not a link but contains link data")] +#[must_use] +pub struct UnexpectedLinkData; + +#[derive(Debug, serde::Serialize)] +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[serde(tag = "type", content = "error", rename_all = "camelCase")] +#[must_use] +pub enum LinkDataStateError { + Missing(Report), + Unexpected(Report), +} + +#[derive(Debug, serde::Serialize)] +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[serde(rename_all = "camelCase")] +pub struct UnexpectedEntityType { + #[cfg_attr(feature = "utoipa", schema(value_type = Vec))] + pub actual: HashSet, + #[cfg_attr(feature = "utoipa", schema(value_type = Vec))] + pub expected: HashSet, +} + +#[derive(Debug, serde::Serialize)] +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[serde(tag = "type", rename_all = "camelCase")] +#[must_use] +pub enum LinkError { + UnexpectedEntityType { data: UnexpectedEntityType }, +} + +#[derive(Debug, serde::Serialize)] +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[serde(tag = "type", rename_all = "camelCase")] +#[must_use] +pub enum LinkTargetError { + UnexpectedEntityType { data: UnexpectedEntityType }, +} + +#[derive(Debug, Default, serde::Serialize)] +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[serde(rename_all = "camelCase")] +#[must_use] +pub struct LinkDataValidationReport { + #[serde(skip_serializing_if = "Option::is_none")] + pub left_entity: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub right_entity: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub link_type: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub target_type: Option, +} + +impl LinkDataValidationReport { + #[must_use] + pub const fn is_valid(&self) -> bool { + self.left_entity.is_none() + && self.right_entity.is_none() + && self.link_type.is_none() + && self.target_type.is_none() + } +} + +#[derive(Debug, Default, serde::Serialize)] +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[serde(rename_all = "camelCase")] +#[must_use] +pub struct LinkValidationReport { + #[serde(skip_serializing_if = "Option::is_none")] + pub link_data: Option, + #[serde(flatten)] + pub link_data_validation: LinkDataValidationReport, +} + +impl LinkValidationReport { + #[must_use] + pub const fn is_valid(&self) -> bool { + self.link_data.is_none() && self.link_data_validation.is_valid() + } +} + +#[derive(Debug, derive_more::Display, derive_more::Error)] +#[display("The entity does not contain any entity types")] +#[must_use] +pub struct EmptyEntityTypes; + +#[derive(Debug, serde::Serialize)] +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[serde(tag = "type", content = "error", rename_all = "camelCase")] +#[must_use] +pub enum EntityTypesError { + Empty(Report), + EntityTypeRetrieval( + #[cfg_attr(feature = "utoipa", schema(value_type = MultiReport))] + Report<[EntityTypeRetrieval]>, + ), + ResolveClosedEntityType(Report), +} + +#[derive(Debug, Default, serde::Serialize)] +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[serde(rename_all = "camelCase")] +#[must_use] +pub struct PropertyMetadataValidationReport; + +impl PropertyMetadataValidationReport { + #[must_use] + #[expect( + clippy::unused_self, + reason = "The struct will be extended in the future" + )] + pub const fn is_valid(&self) -> bool { + true + } +} + +#[derive(Debug, Default, serde::Serialize)] +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[serde(rename_all = "camelCase")] +#[must_use] +pub struct MetadataValidationReport { + #[serde(skip_serializing_if = "Option::is_none")] + pub entity_types: Option, + #[serde(skip_serializing_if = "PropertyMetadataValidationReport::is_valid")] + pub properties: PropertyMetadataValidationReport, +} + +impl MetadataValidationReport { + #[must_use] + pub const fn is_valid(&self) -> bool { + self.entity_types.is_none() && self.properties.is_valid() + } +} + +#[derive(Debug, Default, serde::Serialize)] +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[serde(rename_all = "camelCase")] +#[must_use] +pub struct PropertyValidationReport { + pub error: Option>, +} + +impl PropertyValidationReport { + #[must_use] + pub const fn is_valid(&self) -> bool { + self.error.is_none() + } +} + +#[derive(Debug, Default, serde::Serialize)] +#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))] +#[serde(rename_all = "camelCase")] +#[must_use] +pub struct EntityValidationReport { + #[serde(skip_serializing_if = "PropertyValidationReport::is_valid")] + pub properties: PropertyValidationReport, + #[serde(skip_serializing_if = "LinkValidationReport::is_valid")] + pub link: LinkValidationReport, + #[serde(skip_serializing_if = "MetadataValidationReport::is_valid")] + pub metadata: MetadataValidationReport, +} + +impl EntityValidationReport { + #[must_use] + pub const fn is_valid(&self) -> bool { + self.link.is_valid() && self.metadata.is_valid() + } +} diff --git a/libs/@local/graph/type-fetcher/src/store.rs b/libs/@local/graph/type-fetcher/src/store.rs index ec21866f2d8..be2b441db98 100644 --- a/libs/@local/graph/type-fetcher/src/store.rs +++ b/libs/@local/graph/type-fetcher/src/store.rs @@ -25,9 +25,9 @@ use hash_graph_store::{ UpdateDataTypesParams, }, entity::{ - CountEntitiesParams, CreateEntityParams, EntityStore, GetEntitiesParams, - GetEntitiesResponse, GetEntitySubgraphParams, GetEntitySubgraphResponse, PatchEntityParams, - UpdateEntityEmbeddingsParams, ValidateEntityError, ValidateEntityParams, + CountEntitiesParams, CreateEntityParams, EntityStore, EntityValidationReport, + GetEntitiesParams, GetEntitiesResponse, GetEntitySubgraphParams, GetEntitySubgraphResponse, + PatchEntityParams, UpdateEntityEmbeddingsParams, ValidateEntityParams, }, entity_type::{ ArchiveEntityTypeParams, CountEntityTypesParams, CreateEntityTypeParams, EntityTypeStore, @@ -1204,7 +1204,7 @@ where actor_id: AccountId, consistency: Consistency<'_>, params: Vec>, - ) -> Result<(), Report> { + ) -> HashMap { self.store .validate_entities(actor_id, consistency, params) .await diff --git a/libs/@local/graph/types/rust/src/knowledge/property/visitor.rs b/libs/@local/graph/types/rust/src/knowledge/property/visitor.rs index 72ed7dddb30..17634e1d226 100644 --- a/libs/@local/graph/types/rust/src/knowledge/property/visitor.rs +++ b/libs/@local/graph/types/rust/src/knowledge/property/visitor.rs @@ -17,7 +17,7 @@ use crate::{ PropertyWithMetadataValue, ValueMetadata, error::{Actual, Expected}, }, - ontology::{DataTypeLookup, DataTypeWithMetadata, OntologyTypeProvider, PropertyTypeProvider}, + ontology::{DataTypeLookup, DataTypeWithMetadata, OntologyTypeProvider}, }; #[derive(Debug, thiserror::Error)] @@ -124,7 +124,7 @@ pub trait EntityVisitor: Sized + Send + Sync { type_provider: &P, ) -> impl Future>> + Send where - P: DataTypeLookup + PropertyTypeProvider + Sync, + P: DataTypeLookup + OntologyTypeProvider + Sync, { walk_property(self, schema, property, type_provider) } @@ -140,7 +140,7 @@ pub trait EntityVisitor: Sized + Send + Sync { ) -> impl Future>> + Send where T: PropertyValueSchema + Sync, - P: DataTypeLookup + PropertyTypeProvider + Sync, + P: DataTypeLookup + OntologyTypeProvider + Sync, { walk_array(self, schema, array, type_provider) } @@ -156,7 +156,7 @@ pub trait EntityVisitor: Sized + Send + Sync { ) -> impl Future>> + Send where T: PropertyObjectSchema> + Sync, - P: DataTypeLookup + PropertyTypeProvider + Sync, + P: DataTypeLookup + OntologyTypeProvider + Sync, { walk_object(self, schema, object, type_provider) } @@ -186,7 +186,7 @@ pub trait EntityVisitor: Sized + Send + Sync { type_provider: &P, ) -> impl Future>> + Send where - P: DataTypeLookup + PropertyTypeProvider + Sync, + P: DataTypeLookup + OntologyTypeProvider + Sync, { walk_one_of_array(self, schema, array, type_provider) } @@ -201,7 +201,7 @@ pub trait EntityVisitor: Sized + Send + Sync { type_provider: &P, ) -> impl Future>> + Send where - P: DataTypeLookup + PropertyTypeProvider + Sync, + P: DataTypeLookup + OntologyTypeProvider + Sync, { walk_one_of_object(self, schema, object, type_provider) } @@ -268,7 +268,7 @@ pub async fn walk_property( ) -> Result<(), Report<[TraversalError]>> where V: EntityVisitor, - P: DataTypeLookup + PropertyTypeProvider + Sync, + P: DataTypeLookup + OntologyTypeProvider + Sync, { let mut status = ReportSink::new(); match property { @@ -314,7 +314,7 @@ pub async fn walk_array( where V: EntityVisitor, S: PropertyValueSchema + Sync, - P: DataTypeLookup + PropertyTypeProvider + Sync, + P: DataTypeLookup + OntologyTypeProvider + Sync, { let mut status = ReportSink::new(); @@ -378,7 +378,7 @@ pub async fn walk_object( where V: EntityVisitor, S: PropertyObjectSchema> + Sync, - P: DataTypeLookup + PropertyTypeProvider + Sync, + P: DataTypeLookup + OntologyTypeProvider + Sync, { let mut status = ReportSink::new(); @@ -543,7 +543,7 @@ pub async fn walk_one_of_array( ) -> Result<(), Report<[TraversalError]>> where V: EntityVisitor, - P: DataTypeLookup + PropertyTypeProvider + Sync, + P: DataTypeLookup + OntologyTypeProvider + Sync, { let mut status = ReportSink::new(); let mut passed: usize = 0; @@ -607,7 +607,7 @@ pub async fn walk_one_of_object( ) -> Result<(), Report<[TraversalError]>> where V: EntityVisitor, - P: DataTypeLookup + PropertyTypeProvider + Sync, + P: DataTypeLookup + OntologyTypeProvider + Sync, { let mut status = ReportSink::new(); let mut passed: usize = 0; diff --git a/libs/@local/graph/types/rust/src/ontology/mod.rs b/libs/@local/graph/types/rust/src/ontology/mod.rs index 7e763a7694e..43a9b1ffbd1 100644 --- a/libs/@local/graph/types/rust/src/ontology/mod.rs +++ b/libs/@local/graph/types/rust/src/ontology/mod.rs @@ -10,10 +10,7 @@ use hash_graph_temporal_versioning::{LeftClosedTemporalInterval, TransactionTime use serde::{Deserialize, Serialize}; use time::OffsetDateTime; use type_system::{ - schema::{ - ClosedEntityType, ConversionExpression, DataTypeReference, EntityTypeReference, - PropertyType, PropertyTypeReference, - }, + schema::{DataTypeReference, EntityTypeReference, PropertyTypeReference}, url::{BaseUrl, OntologyTypeVersion, VersionedUrl}, }; @@ -175,42 +172,8 @@ pub trait OntologyTypeProvider { ) -> impl Future>> + Send; } -pub trait DataTypeProvider: OntologyTypeProvider { - fn is_parent_of( - &self, - child: &VersionedUrl, - parent: &BaseUrl, - ) -> impl Future>> + Send; - - fn find_conversion( - &self, - source_data_type_id: &VersionedUrl, - target_data_type_id: &VersionedUrl, - ) -> impl Future< - Output = Result< - impl Borrow>, - Report, - >, - > + Send; -} - -pub trait PropertyTypeProvider: OntologyTypeProvider {} - pub enum EntityTypeVariance { Covariant, Contravariant, Invariant, } - -pub trait EntityTypeProvider: OntologyTypeProvider { - fn is_super_type_of( - &self, - parent: &VersionedUrl, - child: &VersionedUrl, - ) -> impl Future>> + Send; - - fn find_parents( - &self, - entity_types: &[VersionedUrl], - ) -> impl Future, Report>> + Send; -} diff --git a/libs/@local/graph/validation/Cargo.toml b/libs/@local/graph/validation/Cargo.toml index e24f7854d32..4bc008d5bcf 100644 --- a/libs/@local/graph/validation/Cargo.toml +++ b/libs/@local/graph/validation/Cargo.toml @@ -14,18 +14,19 @@ hash-graph-types = { workspace = true, public = true } # Public third-party dependencies # Private workspace dependencies -error-stack = { workspace = true, features = ["hooks"] } +error-stack = { workspace = true, features = ["hooks", "unstable", "futures"] } type-system = { workspace = true } # Private third-party dependencies -futures = { workspace = true } -regex = { workspace = true } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } -thiserror = { workspace = true } -url = { workspace = true } -utoipa = { workspace = true, optional = true } -uuid = { workspace = true, features = ["std"] } +derive_more = { workspace = true, features = ["display", "error"] } +futures = { workspace = true } +regex = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +thiserror = { workspace = true } +url = { workspace = true } +utoipa = { workspace = true, optional = true } +uuid = { workspace = true, features = ["std"] } [dev-dependencies] hash-graph-temporal-versioning = { workspace = true } diff --git a/libs/@local/graph/validation/src/entity_type.rs b/libs/@local/graph/validation/src/entity_type.rs index babf4dc91cf..6855600bfc9 100644 --- a/libs/@local/graph/validation/src/entity_type.rs +++ b/libs/@local/graph/validation/src/entity_type.rs @@ -1,9 +1,15 @@ +use alloc::collections::BTreeSet; use core::borrow::Borrow as _; use std::collections::{HashSet, hash_map::RawEntryMut}; -use error_stack::{Report, ReportSink, ResultExt as _}; +use error_stack::{FutureExt as _, Report, ReportSink, ResultExt as _, TryReportStreamExt as _}; use futures::{StreamExt as _, TryStreamExt as _, stream}; -use hash_graph_store::entity::ValidateEntityComponents; +use hash_graph_store::entity::{ + EntityRetrieval, EntityTypeRetrieval, LinkDataStateError, LinkDataValidationReport, LinkError, + LinkTargetError, LinkValidationReport, LinkedEntityError, MissingLinkData, + PropertyMetadataValidationReport, UnexpectedEntityType, UnexpectedLinkData, + ValidateEntityComponents, +}; use hash_graph_types::{ knowledge::{ entity::{Entity, EntityId}, @@ -17,10 +23,7 @@ use hash_graph_types::{ }, }, }, - ontology::{ - DataTypeLookup, DataTypeWithMetadata, EntityTypeProvider, OntologyTypeProvider, - PropertyTypeProvider, - }, + ontology::{DataTypeLookup, DataTypeWithMetadata, OntologyTypeProvider}, }; use serde_json::Value as JsonValue; use thiserror::Error; @@ -30,19 +33,15 @@ use type_system::{ JsonSchemaValueType, PropertyObjectSchema, PropertyType, PropertyTypeReference, PropertyValueArray, PropertyValueSchema, PropertyValues, ValueOrArray, }, - url::{BaseUrl, OntologyTypeVersion, VersionedUrl}, + url::VersionedUrl, }; -use crate::{EntityProvider, Schema, Validate}; +use crate::{EntityProvider, Validate}; #[derive(Debug, Error)] pub enum EntityValidationError { #[error("The properties of the entity do not match the schema")] InvalidProperties, - #[error("The entity is not a link but contains link data")] - UnexpectedLinkData, - #[error("The entity is a link but does not contain link data")] - MissingLinkData, #[error("Entities without a type are not allowed")] EmptyEntityTypes, #[error("the validator was unable to read the entity type `{ids:?}`")] @@ -60,260 +59,217 @@ pub enum EntityValidationError { impl

Validate for Option<&LinkData> where P: EntityProvider - + EntityTypeProvider + + OntologyTypeProvider + OntologyTypeProvider + DataTypeLookup + Sync, { - type Error = EntityValidationError; + type Report = LinkValidationReport; async fn validate( &self, schema: &ClosedMultiEntityType, components: ValidateEntityComponents, context: &P, - ) -> Result<(), Report<[Self::Error]>> { - if !components.link_data { - return Ok(()); - } - - let mut status = ReportSink::new(); - - // TODO: The link type should be a const but the type system crate does not allow - // to make this a `const` variable. - // see https://linear.app/hash/issue/BP-57 - let link_type_id = VersionedUrl { - base_url: BaseUrl::new( - "https://blockprotocol.org/@blockprotocol/types/entity-type/link/".to_owned(), - ) - .expect("Not a valid URL"), - version: OntologyTypeVersion::new(1), - }; - - let mut is_link = false; - for entity_type in &schema.all_of { - if context - .is_super_type_of(&link_type_id, &entity_type.id) - .await - .change_context_lazy(|| EntityValidationError::EntityTypeRetrieval { - ids: HashSet::from([entity_type.id.clone()]), - })? - { - is_link = true; - break; - } - } + ) -> Self::Report { + let mut validation_report = LinkValidationReport::default(); + let is_link = schema.is_link(); if let Some(link_data) = self { if !is_link { - status.capture(EntityValidationError::UnexpectedLinkData); + validation_report.link_data = Some(LinkDataStateError::Unexpected(Report::new( + UnexpectedLinkData, + ))); } - if let Err(error) = schema.validate_value(*link_data, components, context).await { - status.append(error); + if components.link_validation { + validation_report.link_data_validation = + link_data.validate(schema, components, context).await; } } else if is_link { - status.capture(EntityValidationError::MissingLinkData); + validation_report.link_data = + Some(LinkDataStateError::Missing(Report::new(MissingLinkData))); } - status.finish() + validation_report + } +} + +#[derive(Debug)] +#[must_use] +pub struct PostInsertionEntityValidationReport { + pub link: LinkValidationReport, + pub property_metadata: PropertyMetadataValidationReport, +} + +impl PostInsertionEntityValidationReport { + #[must_use] + pub const fn is_valid(&self) -> bool { + self.link.is_valid() && self.property_metadata.is_valid() } } impl

Validate for Entity where P: EntityProvider - + EntityTypeProvider + + OntologyTypeProvider + OntologyTypeProvider + DataTypeLookup + Sync, { - type Error = EntityValidationError; + type Report = PostInsertionEntityValidationReport; async fn validate( &self, schema: &ClosedMultiEntityType, components: ValidateEntityComponents, context: &P, - ) -> Result<(), Report<[Self::Error]>> { - let mut status = ReportSink::new(); - - if self.metadata.entity_type_ids.is_empty() { - status.capture(EntityValidationError::EmptyEntityTypes); - } - - if components.link_validation { - if let Err(error) = self + ) -> Self::Report { + PostInsertionEntityValidationReport { + link: self .link_data .as_ref() .validate(schema, components, context) - .await - { - status.append(error); - } + .await, + property_metadata: self + .metadata + .properties + .validate(&self.properties, components, context) + .await, } + } +} - if let Err(error) = self - .metadata - .properties - .validate(&self.properties, components, context) - .await - { - status.append(error); - } +async fn read_entity_type

( + entity_id: EntityId, + provider: &P, +) -> Result +where + P: EntityProvider + OntologyTypeProvider + Sync, +{ + let entity = provider + .provide_entity(entity_id) + .await + .change_context(EntityRetrieval { entity_id }) + .map_err(LinkedEntityError::EntityRetrieval)?; + + let entity_types = stream::iter(&entity.borrow().metadata.entity_type_ids) + .then(|entity_type_url| { + provider + .provide_type(entity_type_url) + .change_context_lazy(|| EntityTypeRetrieval { + entity_type_url: entity_type_url.clone(), + }) + }) + .map_ok(|entity_type| entity_type.borrow().clone()) + .try_collect_reports::>() + .await + .map_err(LinkedEntityError::EntityTypeRetrieval)?; - status.finish() - } + ClosedMultiEntityType::from_multi_type_closed_schema(entity_types) + .map_err(LinkedEntityError::ResolveClosedEntityType) } -impl

Schema for ClosedMultiEntityType +impl

Validate for LinkData where - P: EntityProvider + EntityTypeProvider + Sync, + P: EntityProvider + OntologyTypeProvider + Sync, { - type Error = EntityValidationError; + type Report = LinkDataValidationReport; // TODO: validate link data // see https://linear.app/hash/issue/H-972 // TODO: Optimize reading of left/right parent types and/or cache them - #[expect(clippy::too_many_lines)] - async fn validate_value<'a>( - &'a self, - value: &'a LinkData, + async fn validate( + &self, + schema: &ClosedMultiEntityType, _: ValidateEntityComponents, - provider: &'a P, - ) -> Result<(), Report<[EntityValidationError]>> { - let mut status = ReportSink::new(); + context: &P, + ) -> Self::Report { + let mut validation_report = LinkDataValidationReport::default(); - let left_entity = provider - .provide_entity(value.left_entity_id) + let left_entity_type = read_entity_type(self.left_entity_id, context) .await - .change_context_lazy(|| EntityValidationError::EntityRetrieval { - id: value.left_entity_id, - })?; - - let left_entity_type = Self::from_multi_type_closed_schema( - stream::iter(&left_entity.borrow().metadata.entity_type_ids) - .then(|entity_type_url| async { - provider - .provide_type(entity_type_url) - .await - .map(|entity_type| entity_type.borrow().clone()) - }) - .try_collect::>() - .await - .change_context_lazy(|| EntityValidationError::EntityRetrieval { - id: value.left_entity_id, - })?, - ) - .change_context_lazy(|| EntityValidationError::EntityRetrieval { - id: value.left_entity_id, - })?; - - let right_entity = provider - .provide_entity(value.right_entity_id) + .map_err(|link_data_error| { + validation_report.left_entity = Some(link_data_error); + }) + .ok(); + let right_entity_type = read_entity_type(self.right_entity_id, context) .await - .change_context_lazy(|| EntityValidationError::EntityRetrieval { - id: value.right_entity_id, - })?; - - let right_entity_type = Self::from_multi_type_closed_schema( - stream::iter(&right_entity.borrow().metadata.entity_type_ids) - .then(|entity_type_url| async { - provider - .provide_type(entity_type_url) - .await - .map(|entity_type| entity_type.borrow().clone()) - }) - .try_collect::>() - .await - .change_context_lazy(|| EntityValidationError::EntityRetrieval { - id: value.right_entity_id, - })?, - ) - .change_context_lazy(|| EntityValidationError::EntityRetrieval { - id: value.right_entity_id, - })?; - - // We track that at least one link type was found to avoid reporting an error if no - // link type was found. - let mut found_link_target = false; - let entity_type_ids = self + .map_err(|link_data_error| { + validation_report.right_entity = Some(link_data_error); + }) + .ok(); + + // We cannot further validate the links if the left type is not known + let Some(left_entity_type) = left_entity_type else { + return validation_report; + }; + let link_entity_ids = schema .all_of .iter() - .map(|entity_type| entity_type.id.clone()) - .collect::>(); - let parent_entity_type_ids = provider - .find_parents(&entity_type_ids) - .await - .change_context_lazy(|| EntityValidationError::EntityTypeRetrieval { - ids: entity_type_ids.iter().cloned().collect(), - })?; - for link_type_id in entity_type_ids.into_iter().chain(parent_entity_type_ids) { - let Some(maybe_allowed_targets) = left_entity_type.constraints.links.get(&link_type_id) - else { - continue; - }; - - // At least one link type was found - found_link_target = true; - - let Some(allowed_targets) = &maybe_allowed_targets.items else { - // For a given target there was an unconstrained link destination, so we can - // skip the rest of the checks - break; - }; - - // Link destinations are constrained, search for the right entity's type - let mut found_match = false; - 'targets: for allowed_target in &allowed_targets.possibilities { - if right_entity_type - .all_of - .iter() - .any(|entity_type| entity_type.id.base_url == allowed_target.url.base_url) - { - found_match = true; - break; - } - for right_entity_type in &right_entity_type.all_of { - if provider - .is_super_type_of(&allowed_target.url, &right_entity_type.id) - .await - .change_context_lazy(|| EntityValidationError::EntityTypeRetrieval { - ids: HashSet::from([ - right_entity_type.id.clone(), - allowed_target.url.clone(), - ]), - })? - { - found_match = true; - break 'targets; - } - } - } + .flat_map(|entity_type| &entity_type.all_of) + .map(|entity_type| (entity_type.depth, &entity_type.id)) + .collect::>(); - if found_match { - break; - } - status.capture(EntityValidationError::InvalidLinkTargetId { - target_types: right_entity_type - .all_of - .iter() - .map(|entity_type| entity_type.id.clone()) - .collect(), + let Some(maybe_allowed_targets) = link_entity_ids + .iter() + .find_map(|(_, link_type_id)| left_entity_type.constraints.links.get(link_type_id)) + else { + validation_report.link_type = Some(LinkError::UnexpectedEntityType { + data: UnexpectedEntityType { + actual: schema + .all_of + .iter() + .flat_map(|entity_type| &entity_type.all_of) + .map(|entity_type| entity_type.id.clone()) + .collect(), + expected: left_entity_type.constraints.links.keys().cloned().collect(), + }, }); - } + return validation_report; + }; + + let Some(allowed_targets) = &maybe_allowed_targets.items else { + // For a given target there was an unconstrained link destination, so we can + // skip the rest of the checks + return validation_report; + }; + + let Some(right_entity_type) = &right_entity_type else { + // We cannot further validate the links if the right type is not known. + return validation_report; + }; - if !found_link_target { - status.capture(EntityValidationError::InvalidLinkTypeId { - link_types: self - .all_of - .iter() - .map(|entity_type| entity_type.id.clone()) - .collect(), + // Link destinations are constrained, search for the right entity's type + let found_match = allowed_targets.possibilities.iter().any(|allowed_target| { + right_entity_type.all_of.iter().any(|entity_type| { + // We check that the base URL matches for the exact type or the versioned URL + // for the parent types + entity_type.id.base_url == allowed_target.url.base_url + || entity_type + .all_of + .iter() + .any(|entity_type| entity_type.id == allowed_target.url) + }) + }); + + if !found_match { + validation_report.target_type = Some(LinkTargetError::UnexpectedEntityType { + data: UnexpectedEntityType { + actual: link_entity_ids + .into_iter() + .map(|(_, entity_type_id)| entity_type_id.clone()) + .collect(), + expected: allowed_targets + .possibilities + .iter() + .map(|entity_type| entity_type.url.clone()) + .collect(), + }, }); } - status.finish() + validation_report } } @@ -624,7 +580,7 @@ impl EntityVisitor for EntityPreprocessor { ) -> Result<(), Report<[TraversalError]>> where T: PropertyValueSchema + Sync, - P: DataTypeLookup + PropertyTypeProvider + Sync, + P: DataTypeLookup + OntologyTypeProvider + Sync, { let mut status = ReportSink::new(); if let Err(error) = walk_array(self, schema, array, type_provider).await { @@ -662,7 +618,7 @@ impl EntityVisitor for EntityPreprocessor { ) -> Result<(), Report<[TraversalError]>> where T: PropertyObjectSchema> + Sync, - P: DataTypeLookup + PropertyTypeProvider + Sync, + P: DataTypeLookup + OntologyTypeProvider + Sync, { let mut status = ReportSink::new(); if let Err(error) = walk_object(self, schema, object, type_provider).await { diff --git a/libs/@local/graph/validation/src/lib.rs b/libs/@local/graph/validation/src/lib.rs index 54abf47f9bc..d5d7053b749 100644 --- a/libs/@local/graph/validation/src/lib.rs +++ b/libs/@local/graph/validation/src/lib.rs @@ -16,26 +16,15 @@ use error_stack::Report; use hash_graph_store::entity::ValidateEntityComponents; use hash_graph_types::knowledge::entity::{Entity, EntityId}; -pub trait Schema { - type Error: Error + Send + Sync + 'static; - - fn validate_value<'a>( - &'a self, - value: &'a V, - components: ValidateEntityComponents, - provider: &'a P, - ) -> impl Future>> + Send + 'a; -} - pub trait Validate { - type Error: Error + Send + Sync + 'static; + type Report: Send + Sync; fn validate( &self, schema: &S, components: ValidateEntityComponents, context: &C, - ) -> impl Future>> + Send; + ) -> impl Future + Send; } pub trait EntityProvider { @@ -70,10 +59,9 @@ mod tests { visitor::{EntityVisitor as _, TraversalError}, }, ontology::{ - DataTypeLookup, DataTypeMetadata, DataTypeWithMetadata, EntityTypeProvider, - OntologyEditionProvenance, OntologyProvenance, OntologyTemporalMetadata, - OntologyTypeClassificationMetadata, OntologyTypeProvider, OntologyTypeRecordId, - PropertyTypeProvider, ProvidedOntologyEditionProvenance, + DataTypeLookup, DataTypeMetadata, DataTypeWithMetadata, OntologyEditionProvenance, + OntologyProvenance, OntologyTemporalMetadata, OntologyTypeClassificationMetadata, + OntologyTypeProvider, OntologyTypeRecordId, ProvidedOntologyEditionProvenance, }, owned_by_id::OwnedById, }; @@ -191,27 +179,6 @@ mod tests { } } - impl EntityTypeProvider for Provider { - #[expect(refining_impl_trait)] - async fn is_super_type_of( - &self, - _: &VersionedUrl, - _: &VersionedUrl, - ) -> Result> { - // Not used in tests - Ok(false) - } - - #[expect(refining_impl_trait)] - async fn find_parents( - &self, - _: &[VersionedUrl], - ) -> Result, Report> { - // Not used in tests - Ok(Vec::new()) - } - } - impl OntologyTypeProvider for Provider { type Value = Arc; @@ -250,8 +217,6 @@ mod tests { } } - impl PropertyTypeProvider for Provider {} - impl DataTypeLookup for Provider { type ClosedDataType = Arc; type DataTypeWithMetadata = Arc; diff --git a/libs/@local/graph/validation/src/property.rs b/libs/@local/graph/validation/src/property.rs index 672222b4293..f3b29147f43 100644 --- a/libs/@local/graph/validation/src/property.rs +++ b/libs/@local/graph/validation/src/property.rs @@ -1,30 +1,26 @@ -use error_stack::{Report, ReportSink}; -use hash_graph_store::entity::ValidateEntityComponents; +use hash_graph_store::entity::{PropertyMetadataValidationReport, ValidateEntityComponents}; use hash_graph_types::knowledge::property::{PropertyMetadataObject, PropertyObject}; -use crate::{EntityValidationError, Validate}; +use crate::Validate; impl

Validate for PropertyMetadataObject where P: Sync, { - type Error = EntityValidationError; + type Report = PropertyMetadataValidationReport; async fn validate( &self, _object: &PropertyObject, _components: ValidateEntityComponents, _provider: &P, - ) -> Result<(), Report<[Self::Error]>> { - let status = ReportSink::new(); - + ) -> Self::Report { // TODO: Validate metadata // - Check that all metadata keys are valid see: // - see: https://linear.app/hash/issue/H-2799/validate-entity-property-metadata-layout // - Check that all metadata values are valid // - see: https://linear.app/hash/issue/H-2800/validate-that-allowed-data-types-are-either-unambiguous-or-a-data-type // - see: https://linear.app/hash/issue/H-2801/validate-data-type-in-entity-property-metadata - - status.finish() + PropertyMetadataValidationReport {} } } diff --git a/libs/chonky/README.md b/libs/chonky/README.md index 2d4ec027f9f..9c252960ad8 100644 --- a/libs/chonky/README.md +++ b/libs/chonky/README.md @@ -42,7 +42,7 @@ rm -rf $temp_dir To link the library dynamically, don't enable the `static`. The binary will read `PDFIUM_DYNAMIC_LIB_PATH` to search for the library. If the variable is not set it will use `libs/`: ```sh -export PDFIUM_DYNAMIC_LIB_PATH="${pwd}/libs/" +export PDFIUM_DYNAMIC_LIB_PATH="$(pwd)/libs/" cargo build ``` @@ -61,7 +61,7 @@ rm -rf $temp_dir To link the library statically, enable the `static` feature by passing `--features static` to any `cargo` invocation. When building the library it will search for `PDFIUM_STATIC_LIB_PATH`. For example if the library is located at `libs/libpdfium.a` you can build the library with: ```sh -export PDFIUM_STATIC_LIB_PATH="${pwd}/libs/" +export PDFIUM_STATIC_LIB_PATH="$(pwd)/libs/" cargo build --features static ``` diff --git a/tests/graph/http/.justfile b/tests/graph/http/.justfile deleted file mode 100755 index d4d1426b85d..00000000000 --- a/tests/graph/http/.justfile +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env just --justfile - -set fallback - -repo := `git rev-parse --show-toplevel` -profile := env_var_or_default('PROFILE', "dev") - -[private] -default: - @just usage - -[private] -test-integration *arguments: - @just yarn httpyac send --all {{repo}}/apps/hash-graph/libs/api/tests/friendship.http - @just yarn graph:reset-database - @just yarn httpyac send --all {{repo}}/apps/hash-graph/libs/api/tests/circular-links.http - @just yarn graph:reset-database - @just yarn httpyac send --all {{repo}}/apps/hash-graph/libs/api/tests/ambiguous.http - @just yarn graph:reset-database diff --git a/tests/graph/http/test.sh b/tests/graph/http/test.sh index 2c4095333cd..f97a1f651e5 100755 --- a/tests/graph/http/test.sh +++ b/tests/graph/http/test.sh @@ -2,12 +2,12 @@ set -eux -yarn reset-database -yarn httpyac send --all tests/friendship.http -yarn reset-database -yarn httpyac send --all tests/circular-links.http -yarn reset-database -yarn httpyac send --all tests/ambiguous.http -yarn reset-database -yarn httpyac send --all tests/link-inheritance.http -yarn reset-database +yarn reset-database -o none +yarn httpyac send --all tests/friendship.http -o none +yarn reset-database -o none +yarn httpyac send --all tests/circular-links.http -o none +yarn reset-database -o none +yarn httpyac send --all tests/ambiguous.http -o none +yarn reset-database -o none +yarn httpyac send --all tests/link-inheritance.http -o none +yarn reset-database -o none diff --git a/tests/graph/http/tests/friendship.http b/tests/graph/http/tests/friendship.http index c2868c8969e..508f94a7d02 100644 --- a/tests/graph/http/tests/friendship.http +++ b/tests/graph/http/tests/friendship.http @@ -1273,7 +1273,8 @@ X-Authenticated-User-Actor-Id: {{account_id}} > {% client.test("status", function() { - client.assert(response.status === 204, "Response status is not 204"); + client.assert(response.status === 200, "Response status is not 200"); + client.assert(Object.keys(response.body).length === 0, "No validation errors expected"); }); %} @@ -1401,7 +1402,8 @@ X-Authenticated-User-Actor-Id: {{account_id}} > {% client.test("status", function() { - client.assert(response.status === 204, "Response status is not 204"); + client.assert(response.status === 200, "Response status is not 200"); + client.assert(Object.keys(response.body).length === 0, "No validation errors expected"); }); %} @@ -1624,7 +1626,8 @@ X-Authenticated-User-Actor-Id: {{account_id}} > {% client.test("status", function() { - client.assert(response.status === 204, "Response status is not 204"); + client.assert(response.status === 200, "Response status is not 200"); + client.assert(Object.keys(response.body).length === 0, "No validation errors expected"); }); %} diff --git a/tests/graph/http/tests/link-inheritance.http b/tests/graph/http/tests/link-inheritance.http index e0bc0dc9eff..6c5085fb60e 100644 --- a/tests/graph/http/tests/link-inheritance.http +++ b/tests/graph/http/tests/link-inheritance.http @@ -728,7 +728,7 @@ X-Authenticated-User-Actor-Id: {{account_id}} > {% client.test("status", function() { - client.assert(response.status === 500, "Response status is not 200"); + client.assert(response.status === 400, "Response status is not 400"); }); %} @@ -818,7 +818,7 @@ X-Authenticated-User-Actor-Id: {{account_id}} > {% client.test("status", function() { - client.assert(response.status === 500, "Response status is not 200"); + client.assert(response.status === 400, "Response status is not 400"); }); %} @@ -998,7 +998,7 @@ X-Authenticated-User-Actor-Id: {{account_id}} > {% client.test("status", function() { - client.assert(response.status === 500, "Response status is not 200"); + client.assert(response.status === 400, "Response status is not 400"); }); %} @@ -1088,7 +1088,7 @@ X-Authenticated-User-Actor-Id: {{account_id}} > {% client.test("status", function() { - client.assert(response.status === 500, "Response status is not 200"); + client.assert(response.status === 400, "Response status is not 400"); }); %} @@ -1118,7 +1118,7 @@ X-Authenticated-User-Actor-Id: {{account_id}} > {% client.test("status", function() { - client.assert(response.status === 500, "Response status is not 200"); + client.assert(response.status === 400, "Response status is not 400"); }); %} diff --git a/tests/graph/integration/postgres/lib.rs b/tests/graph/integration/postgres/lib.rs index 45e6ba483b2..ae2832d888f 100644 --- a/tests/graph/integration/postgres/lib.rs +++ b/tests/graph/integration/postgres/lib.rs @@ -53,9 +53,9 @@ use hash_graph_store::{ UpdateDataTypesParams, }, entity::{ - CountEntitiesParams, CreateEntityParams, EntityStore, GetEntitiesParams, - GetEntitiesResponse, GetEntitySubgraphParams, GetEntitySubgraphResponse, PatchEntityParams, - UpdateEntityEmbeddingsParams, ValidateEntityError, ValidateEntityParams, + CountEntitiesParams, CreateEntityParams, EntityStore, EntityValidationReport, + GetEntitiesParams, GetEntitiesResponse, GetEntitySubgraphParams, GetEntitySubgraphResponse, + PatchEntityParams, UpdateEntityEmbeddingsParams, ValidateEntityParams, }, entity_type::{ ArchiveEntityTypeParams, CountEntityTypesParams, CreateEntityTypeParams, EntityTypeStore, @@ -708,7 +708,7 @@ where actor_id: AccountId, consistency: Consistency<'_>, params: Vec>, - ) -> Result<(), Report> { + ) -> HashMap { self.store .validate_entities(actor_id, consistency, params) .await diff --git a/yarn.lock b/yarn.lock index 81ead642ef5..c5902054bc2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12445,6 +12445,7 @@ __metadata: "@rust/hash-graph-type-defs": "npm:0.0.0-private" "@rust/hash-graph-type-fetcher": "npm:0.0.0-private" "@rust/hash-graph-types": "npm:0.0.0-private" + "@rust/hash-graph-validation": "npm:0.0.0-private" "@rust/hash-status": "npm:0.0.0-private" "@rust/hash-temporal-client": "npm:0.0.0-private" languageName: unknown From a7f39369759737630d7cea74946b75a703665cad Mon Sep 17 00:00:00 2001 From: Tim Diekmann <21277928+TimDiekmann@users.noreply.github.com> Date: Sun, 8 Dec 2024 14:17:31 +0100 Subject: [PATCH 24/24] H-3745: Upgrade recursive dependencies and TemporalIO dependency (#5837) --- Cargo.lock | 504 +++++------------- Cargo.toml | 178 +++---- .../type-system/rust/Cargo.toml | 4 +- libs/@local/hql/diagnostics/Cargo.toml | 2 +- libs/@local/temporal-client/Cargo.toml | 14 +- libs/@local/temporal-client/src/ai.rs | 4 +- libs/@local/temporal-client/src/lib.rs | 4 +- 7 files changed, 249 insertions(+), 461 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 18d640aa71a..2fd54215e7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -795,34 +795,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "axum" -version = "0.6.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" -dependencies = [ - "async-trait", - "axum-core 0.3.4", - "bitflags 1.3.2", - "bytes", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.30", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "sync_wrapper 0.1.2", - "tower 0.4.13", - "tower-layer", - "tower-service", -] - [[package]] name = "axum" version = "0.7.9" @@ -830,7 +802,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ "async-trait", - "axum-core 0.4.5", + "axum-core", "bytes", "futures-util", "http 1.2.0", @@ -849,7 +821,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper 1.0.1", + "sync_wrapper 1.0.2", "tokio", "tower 0.5.1", "tower-layer", @@ -857,23 +829,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "axum-core" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "mime", - "rustversion", - "tower-layer", - "tower-service", -] - [[package]] name = "axum-core" version = "0.4.5" @@ -889,7 +844,7 @@ dependencies = [ "mime", "pin-project-lite", "rustversion", - "sync_wrapper 1.0.1", + "sync_wrapper 1.0.2", "tower-layer", "tower-service", "tracing", @@ -1155,9 +1110,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.1.28" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" dependencies = [ "jobserver", "libc", @@ -1217,9 +1172,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -1227,7 +1182,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -1287,7 +1242,7 @@ dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.1", + "strsim", "terminal_size", ] @@ -1377,12 +1332,6 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "convert_case" version = "0.6.0" @@ -1579,38 +1528,14 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "darling" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" -dependencies = [ - "darling_core 0.14.4", - "darling_macro 0.14.4", -] - [[package]] name = "darling" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ - "darling_core 0.20.10", - "darling_macro 0.20.10", -] - -[[package]] -name = "darling_core" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.10.0", - "syn 1.0.109", + "darling_core", + "darling_macro", ] [[package]] @@ -1623,28 +1548,17 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim 0.11.1", + "strsim", "syn 2.0.90", ] -[[package]] -name = "darling_macro" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" -dependencies = [ - "darling_core 0.14.4", - "quote", - "syn 1.0.109", -] - [[package]] name = "darling_macro" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ - "darling_core 0.20.10", + "darling_core", "quote", "syn 2.0.90", ] @@ -1810,45 +1724,32 @@ dependencies = [ [[package]] name = "derive_builder" -version = "0.12.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" dependencies = [ "derive_builder_macro", ] [[package]] name = "derive_builder_core" -version = "0.12.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ - "darling 0.14.4", + "darling", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.90", ] [[package]] name = "derive_builder_macro" -version = "0.12.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 1.0.109", -] - -[[package]] -name = "derive_more" -version = "0.99.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" -dependencies = [ - "convert_case 0.4.0", - "proc-macro2", - "quote", - "rustc_version", "syn 2.0.90", ] @@ -2602,7 +2503,7 @@ version = "0.0.0" dependencies = [ "bytes", "derive-where", - "derive_more 1.0.0", + "derive_more", "error-stack", "futures", "harpc-codec", @@ -2641,7 +2542,7 @@ version = "0.0.0" dependencies = [ "bytes", "bytes-utils", - "derive_more 1.0.0", + "derive_more", "error-stack", "futures", "futures-core", @@ -2682,7 +2583,7 @@ version = "0.0.0" dependencies = [ "bytes", "derive-where", - "derive_more 1.0.0", + "derive_more", "error-stack", "frunk", "frunk_core", @@ -2719,7 +2620,7 @@ name = "harpc-tower" version = "0.0.0" dependencies = [ "bytes", - "derive_more 1.0.0", + "derive_more", "error-stack", "futures", "futures-core", @@ -2786,7 +2687,7 @@ dependencies = [ name = "hash-graph" version = "0.0.0" dependencies = [ - "axum 0.7.9", + "axum", "clap", "clap_complete", "error-stack", @@ -2821,11 +2722,11 @@ name = "hash-graph-api" version = "0.0.0" dependencies = [ "async-trait", - "axum 0.7.9", - "axum-core 0.4.5", + "axum", + "axum-core", "bytes", "derive-where", - "derive_more 1.0.0", + "derive_more", "error-stack", "frunk", "futures", @@ -2939,7 +2840,7 @@ dependencies = [ "bytes", "clap", "clap_complete", - "derive_more 1.0.0", + "derive_more", "error-stack", "futures", "hash-graph-migrations-macros", @@ -2957,8 +2858,8 @@ dependencies = [ name = "hash-graph-migrations-macros" version = "0.0.0" dependencies = [ - "convert_case 0.6.0", - "derive_more 1.0.0", + "convert_case", + "derive_more", "proc-macro-error2", "proc-macro2", "quote", @@ -3011,7 +2912,7 @@ version = "0.0.0" dependencies = [ "bytes", "derive-where", - "derive_more 1.0.0", + "derive_more", "error-stack", "futures", "hash-graph-authorization", @@ -3053,7 +2954,7 @@ version = "0.0.0" name = "hash-graph-test-server" version = "0.0.0" dependencies = [ - "axum 0.7.9", + "axum", "error-stack", "futures", "hash-codec", @@ -3083,7 +2984,7 @@ dependencies = [ name = "hash-graph-type-fetcher" version = "0.0.0" dependencies = [ - "derive_more 1.0.0", + "derive_more", "error-stack", "futures", "hash-graph-authorization", @@ -3130,7 +3031,7 @@ dependencies = [ name = "hash-graph-validation" version = "0.0.0" dependencies = [ - "derive_more 1.0.0", + "derive_more", "error-stack", "futures", "hash-graph-store", @@ -3196,7 +3097,7 @@ version = "0.0.0" dependencies = [ "clap", "clap_builder", - "derive_more 1.0.0", + "derive_more", "error-stack", "opentelemetry 0.27.1", "opentelemetry-otlp", @@ -3335,15 +3236,6 @@ dependencies = [ "digest", ] -[[package]] -name = "home" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" -dependencies = [ - "windows-sys 0.52.0", -] - [[package]] name = "hostname" version = "0.3.1" @@ -3566,7 +3458,7 @@ dependencies = [ "hyper 0.14.30", "log", "rustls 0.21.12", - "rustls-native-certs", + "rustls-native-certs 0.6.3", "tokio", "tokio-rustls 0.24.1", ] @@ -3589,18 +3481,6 @@ dependencies = [ "webpki-roots", ] -[[package]] -name = "hyper-timeout" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" -dependencies = [ - "hyper 0.14.30", - "pin-project-lite", - "tokio", - "tokio-io-timeout", -] - [[package]] name = "hyper-timeout" version = "0.5.1" @@ -3644,7 +3524,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core 0.52.0", + "windows-core 0.51.1", ] [[package]] @@ -3982,9 +3862,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jobserver" @@ -4003,9 +3883,9 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" [[package]] name = "js-sys" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ "once_cell", "wasm-bindgen", @@ -4147,9 +4027,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.159" +version = "0.2.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "libfuzzer-sys" @@ -5094,22 +4974,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "opentelemetry" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e32339a5dc40459130b3bd269e9892439f55b33e772d2a9d402a789baaf4e8a" -dependencies = [ - "futures-core", - "futures-sink", - "indexmap 2.6.0", - "js-sys", - "once_cell", - "pin-project-lite", - "thiserror 1.0.69", - "urlencoding", -] - [[package]] name = "opentelemetry" version = "0.26.0" @@ -5150,10 +5014,10 @@ dependencies = [ "opentelemetry 0.27.1", "opentelemetry-proto", "opentelemetry_sdk 0.27.1", - "prost 0.13.3", + "prost", "thiserror 1.0.69", "tokio", - "tonic 0.12.3", + "tonic", ] [[package]] @@ -5164,8 +5028,8 @@ checksum = "a6e05acbfada5ec79023c85368af14abd0b307c015e9064d249b2a950ef459a6" dependencies = [ "opentelemetry 0.27.1", "opentelemetry_sdk 0.27.1", - "prost 0.13.3", - "tonic 0.12.3", + "prost", + "tonic", ] [[package]] @@ -5361,7 +5225,7 @@ dependencies = [ "console_error_panic_hook", "console_log", "image", - "itertools 0.13.0", + "itertools 0.10.5", "js-sys", "libloading", "log", @@ -5622,16 +5486,6 @@ dependencies = [ "yansi", ] -[[package]] -name = "prettyplease" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" -dependencies = [ - "proc-macro2", - "syn 1.0.109", -] - [[package]] name = "prettyplease" version = "0.2.22" @@ -5754,16 +5608,6 @@ dependencies = [ "unarray", ] -[[package]] -name = "prost" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" -dependencies = [ - "bytes", - "prost-derive 0.11.9", -] - [[package]] name = "prost" version = "0.13.3" @@ -5771,42 +5615,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" dependencies = [ "bytes", - "prost-derive 0.13.3", + "prost-derive", ] [[package]] name = "prost-build" -version = "0.11.9" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" +checksum = "0c1318b19085f08681016926435853bbf7858f9c082d0999b80550ff5d9abe15" dependencies = [ "bytes", "heck 0.4.1", "itertools 0.10.5", - "lazy_static", "log", "multimap", + "once_cell", "petgraph", - "prettyplease 0.1.25", - "prost 0.11.9", + "prettyplease", + "prost", "prost-types", "regex", - "syn 1.0.109", + "syn 2.0.90", "tempfile", - "which", -] - -[[package]] -name = "prost-derive" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" -dependencies = [ - "anyhow", - "itertools 0.10.5", - "proc-macro2", - "quote", - "syn 1.0.109", ] [[package]] @@ -5816,7 +5646,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.10.5", "proc-macro2", "quote", "syn 2.0.90", @@ -5824,22 +5654,22 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.11.9" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670" dependencies = [ - "prost 0.11.9", + "prost", ] [[package]] name = "prost-wkt" -version = "0.4.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562788060bcf2bfabe055194bd991ed2442457661744c88e0a0828ff9a08c08b" +checksum = "a8d84e2bee181b04c2bac339f2bfe818c46a99750488cc6728ce4181d5aa8299" dependencies = [ "chrono", "inventory", - "prost 0.11.9", + "prost", "serde", "serde_derive", "serde_json", @@ -5848,12 +5678,12 @@ dependencies = [ [[package]] name = "prost-wkt-build" -version = "0.4.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4dca8bcead3b728a6a7da017cc95e7f4cb2320ec4f6896bc593a1c4700f7328" +checksum = "8a669d5acbe719010c6f62a64e6d7d88fdedc1fe46e419747949ecb6312e9b14" dependencies = [ "heck 0.4.1", - "prost 0.11.9", + "prost", "prost-build", "prost-types", "quote", @@ -5861,12 +5691,12 @@ dependencies = [ [[package]] name = "prost-wkt-types" -version = "0.4.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2377c5680f2342871823045052e791b4487f7c90aae17e0feaee24cf59578a34" +checksum = "01ef068e9b82e654614b22e6b13699bd545b6c0e2e721736008b00b38aeb4f64" dependencies = [ "chrono", - "prost 0.11.9", + "prost", "prost-build", "prost-types", "prost-wkt", @@ -6280,7 +6110,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.1", + "sync_wrapper 1.0.2", "tokio", "tokio-rustls 0.26.0", "tokio-util", @@ -6451,6 +6281,19 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -6498,9 +6341,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "rw-stream-sink" @@ -6865,7 +6708,7 @@ version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" dependencies = [ - "darling 0.20.10", + "darling", "proc-macro2", "quote", "syn 2.0.90", @@ -7072,12 +6915,6 @@ dependencies = [ "unicode-properties", ] -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "strsim" version = "0.11.1" @@ -7158,9 +6995,9 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "sync_wrapper" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] @@ -7293,27 +7130,29 @@ dependencies = [ [[package]] name = "temporal-client" version = "0.1.0" -source = "git+https://github.com/temporalio/sdk-core?rev=7e3c23f#7e3c23fd90daae19c8d8305f20b3530d6d2cd252" +source = "git+https://github.com/temporalio/sdk-core?rev=4a2368d#4a2368d19f57e971ca9b2465f1dbeede7a861c34" dependencies = [ "anyhow", "async-trait", "backoff", + "base64 0.22.1", "derive_builder", - "derive_more 0.99.18", - "futures", + "derive_more", "futures-retry", - "http 0.2.12", - "once_cell", - "opentelemetry 0.21.0", + "futures-util", + "http 1.2.0", + "http-body-util", + "hyper 1.5.1", + "hyper-util", "parking_lot", "prost-types", "slotmap", "temporal-sdk-core-api", "temporal-sdk-core-protos", - "thiserror 1.0.69", + "thiserror 2.0.4", "tokio", - "tonic 0.9.2", - "tower 0.4.13", + "tonic", + "tower 0.5.1", "tracing", "url", "uuid", @@ -7322,18 +7161,17 @@ dependencies = [ [[package]] name = "temporal-sdk-core-api" version = "0.1.0" -source = "git+https://github.com/temporalio/sdk-core?rev=7e3c23f#7e3c23fd90daae19c8d8305f20b3530d6d2cd252" +source = "git+https://github.com/temporalio/sdk-core?rev=4a2368d#4a2368d19f57e971ca9b2465f1dbeede7a861c34" dependencies = [ "async-trait", "derive_builder", - "derive_more 0.99.18", + "derive_more", + "prost", "prost-types", - "serde", "serde_json", "temporal-sdk-core-protos", - "thiserror 1.0.69", - "tokio", - "tonic 0.9.2", + "thiserror 2.0.4", + "tonic", "tracing-core", "url", ] @@ -7341,19 +7179,21 @@ dependencies = [ [[package]] name = "temporal-sdk-core-protos" version = "0.1.0" -source = "git+https://github.com/temporalio/sdk-core?rev=7e3c23f#7e3c23fd90daae19c8d8305f20b3530d6d2cd252" +source = "git+https://github.com/temporalio/sdk-core?rev=4a2368d#4a2368d19f57e971ca9b2465f1dbeede7a861c34" dependencies = [ "anyhow", - "base64 0.21.7", - "derive_more 0.99.18", - "prost 0.11.9", + "base64 0.22.1", + "derive_more", + "prost", + "prost-build", + "prost-types", "prost-wkt", "prost-wkt-build", "prost-wkt-types", "serde", "serde_json", - "thiserror 1.0.69", - "tonic 0.9.2", + "thiserror 2.0.4", + "tonic", "tonic-build", ] @@ -7405,11 +7245,11 @@ version = "7.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d547323725fa9910b0b3146eeaedd471c000dd75eb107c350aa8e1aebef2d7a" dependencies = [ - "darling 0.20.10", + "darling", "heck 0.5.0", "itertools 0.13.0", "once_cell", - "prettyplease 0.2.22", + "prettyplease", "proc-macro2", "quote", "syn 2.0.90", @@ -7604,16 +7444,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "tokio-io-timeout" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" -dependencies = [ - "pin-project-lite", - "tokio", -] - [[package]] name = "tokio-macros" version = "2.4.0" @@ -7764,38 +7594,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "tonic" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" -dependencies = [ - "async-stream", - "async-trait", - "axum 0.6.20", - "base64 0.21.7", - "bytes", - "futures-core", - "futures-util", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.30", - "hyper-timeout 0.4.1", - "percent-encoding", - "pin-project", - "prost 0.11.9", - "rustls-native-certs", - "rustls-pemfile 1.0.4", - "tokio", - "tokio-rustls 0.24.1", - "tokio-stream", - "tower 0.4.13", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "tonic" version = "0.12.3" @@ -7804,7 +7602,7 @@ checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" dependencies = [ "async-stream", "async-trait", - "axum 0.7.9", + "axum", "base64 0.22.1", "bytes", "h2 0.4.6", @@ -7812,13 +7610,16 @@ dependencies = [ "http-body 1.0.1", "http-body-util", "hyper 1.5.1", - "hyper-timeout 0.5.1", + "hyper-timeout", "hyper-util", "percent-encoding", "pin-project", - "prost 0.13.3", + "prost", + "rustls-native-certs 0.8.0", + "rustls-pemfile 2.2.0", "socket2", "tokio", + "tokio-rustls 0.26.0", "tokio-stream", "tower 0.4.13", "tower-layer", @@ -7828,15 +7629,16 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.9.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6fdaae4c2c638bb70fe42803a26fbd6fc6ac8c72f5c59f67ecc2a2dcabf4b07" +checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11" dependencies = [ - "prettyplease 0.1.25", + "prettyplease", "proc-macro2", "prost-build", + "prost-types", "quote", - "syn 1.0.109", + "syn 2.0.90", ] [[package]] @@ -8120,7 +7922,7 @@ version = "0.0.0" dependencies = [ "bytes", "console_error_panic_hook", - "derive_more 1.0.0", + "derive_more", "email_address", "error-stack", "futures", @@ -8470,9 +8272,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -8483,13 +8285,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn 2.0.90", @@ -8498,9 +8299,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.47" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" dependencies = [ "cfg-if", "js-sys", @@ -8511,9 +8312,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -8521,9 +8322,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", @@ -8534,19 +8335,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "wasm-bindgen-test" -version = "0.3.47" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d919bb60ebcecb9160afee6c71b43a58a4f0517a2de0054cd050d02cec08201" +checksum = "c61d44563646eb934577f2772656c7ad5e9c90fac78aa8013d776fcdaf24625d" dependencies = [ "js-sys", "minicov", - "once_cell", "scoped-tls", "wasm-bindgen", "wasm-bindgen-futures", @@ -8555,9 +8355,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.47" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222ebde6ea87fbfa6bdd2e9f1fd8a91d60aee5db68792632176c4e16a74fc7d8" +checksum = "54171416ce73aa0b9c377b51cc3cb542becee1cd678204812e8392e5b0e4a031" dependencies = [ "proc-macro2", "quote", @@ -8579,9 +8379,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", @@ -8612,18 +8412,6 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix", -] - [[package]] name = "whoami" version = "1.5.2" @@ -8663,7 +8451,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 91baea6ec27..e49822acf16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,9 +92,9 @@ ahash = { version = "=0.8.11", default-features = false } ariadne = { version = "=0.5.0", default-features = false } aws-types = { version = "=1.3.3", default-features = false } axum = { version = "0.7.5" } -axum-core = { version = "0.4.3" } +axum-core = { version = "0.4.5" } bumpalo = { version = "=3.16.0", default-features = false } -bytes = { version = "1.6.0" } +bytes = { version = "1.9.0" } clap_builder = { version = "=4.5.23", default-features = false, features = ["std"] } criterion = { version = "=0.5.1" } deadpool = { version = "=0.12.1", default-features = false } @@ -148,93 +148,93 @@ utoipa = { version = "=4.2.3", default-features = false } uuid = { version = "=1.11.0", default-features = false } # Shared third-party dependencies -ansi-to-html = { version = "=0.2.2", default-features = false } -anstyle = { version = "=1.0.10", default-features = false } -anstyle-yansi = { version = "=2.0.2", default-features = false } -approx = { version = "=0.5.1", default-features = false } -async-scoped = { version = "=0.9.0", default-features = false } -async-trait = { version = "=0.1.83", default-features = false } -aws-config = { version = "=1.5.10" } -aws-sdk-s3 = { version = "=1.65.0", default-features = false } -bitvec = { version = "=1.0.1", default-features = false } -bytes-utils = { version = "=0.1.4", default-features = false } -clap = { version = "=4.5.23", features = ["color", "error-context", "help", "std", "suggestions", "usage"] } -clap_complete = { version = "=4.5.38", default-features = false } -convert_case = { version = "=0.6.0", default-features = false } -criterion-macro = { version = "=0.4.0", default-features = false } -derive-where = { version = "=1.2.7", default-features = false, features = ["nightly"] } -derive_more = { version = "=1.0.0", default-features = false } -dotenv-flow = { version = "=0.16.2", default-features = false } -expect-test = { version = "=1.5.0", default-features = false } -frunk_core = { version = "0.4.3", default-features = false } -futures = { version = "=0.3.31", default-features = false } -hifijson = { version = "=0.2.2", default-features = false } -humansize = { version = "=2.1.3", default-features = false } -hyper = { version = "=1.5.1", default-features = false } -include_dir = { version = "=0.7.4", default-features = false } -insta = { version = "=1.41.1", default-features = false } -itertools = { version = "0.13.0", default-features = false } -jsonschema = { version = "=0.26.1", default-features = false } -justjson = { version = "=0.3.0", default-features = false } -lexical = { version = "=7.0.3", default-features = false } -libp2p = { version = "=0.54.1", default-features = false } -libp2p-stream = { version = "=0.2.0-alpha", default-features = false } -logos = { version = "=0.15.0", default-features = false } -memchr = { version = "=2.7.4", default-features = false } -mimalloc = { version = "=0.1.43", default-features = false } -mime = { version = "=0.3.17", default-features = false } -num-traits = { version = "=0.2.19", default-features = false } -once_cell = { version = "=1.20.2", default-features = false } -opentelemetry = { version = "=0.27.1", default-features = false } -opentelemetry-otlp = { version = "=0.27.0", default-features = false } -opentelemetry_sdk = { version = "=0.27.1", default-features = false } -orx-concurrent-vec = { version = "=3.0.2", default-features = false } -owo-colors = { version = "=4.1.0", default-features = false } -paste = { version = "=1.0.15", default-features = false } -pin-project = { version = "=1.1.7", default-features = false } -pin-project-lite = { version = "=0.2.15", default-features = false } -postgres-protocol = { version = "=0.6.7", default-features = false } -pretty_assertions = { version = "=1.4.1", default-features = false, features = ["alloc"] } -proc-macro-error2 = { version = "=2.0.1", default-features = false } -proc-macro2 = { version = "=1.0.92", default-features = false } -proptest = { version = "=1.5.0", default-features = false, features = ["alloc"] } -quote = { version = "=1.0.37", default-features = false } -rand = { version = "=0.8.5", default-features = false } -refinery = { version = "=0.8.14", default-features = false } -rustc_version = { version = "=0.4.1", default-features = false } -scc = { version = "=2.2.5", default-features = false } -sentry = { version = "=0.35.0", default-features = false, features = ["backtrace", "contexts", "debug-images", "panic", "reqwest", "rustls", "tower-http", "tracing"] } -seq-macro = { version = "=0.3.5", default-features = false } -serde_plain = { version = "=1.0.2", default-features = false } -serde_with = { version = "=3.11.0", default-features = false } -sha2 = { version = "=0.10.8", default-features = false } -similar-asserts = { version = "=1.6.0", default-features = false } -supports-color = { version = "=3.0.2", default-features = false } -supports-unicode = { version = "=3.0.0", default-features = false } -syn = { version = "=2.0.90", default-features = false } -tachyonix = { version = "=0.3.1", default-features = false } -tarpc = { version = "=0.35.0", default-features = false } -temporal-io-client = { package = "temporal-client", git = "https://github.com/temporalio/sdk-core", rev = "7e3c23f" } -temporal-io-sdk-core-protos = { package = "temporal-sdk-core-protos", git = "https://github.com/temporalio/sdk-core", rev = "7e3c23f" } -test-fuzz = { version = "=7.0.1", default-features = false } -test-log = { version = "=0.2.16", default-features = false } -test-strategy = { version = "=0.4.0", default-features = false } -thiserror = { version = "=2.0.4", default-features = false } -tokio-stream = { version = "=0.1.17", default-features = false } -tokio-test = { version = "=0.4.4", default-features = false } -tower = { version = "=0.5.1", default-features = false } -tower-test = { version = "=0.4.0", default-features = false } -tracing = { version = "=0.1.41", default-features = false } -tracing-error = { version = "=0.2.1", default-features = false } -tracing-flame = { version = "=0.2.0", default-features = false } -tracing-opentelemetry = { version = "=0.28.0", default-features = false } -trait-variant = { version = "=0.1.2", default-features = false } -trybuild = { version = "=1.0.101", default-features = false } -tsify = { version = "=0.4.5", default-features = false } -unicode-ident = { version = "=1.0.14", default-features = false } -virtue = { version = "=0.0.18", default-features = false } -walkdir = { version = "=2.5.0", default-features = false } -winnow = { version = "=0.6.20", default-features = false } +ansi-to-html = { version = "=0.2.2", default-features = false } +anstyle = { version = "=1.0.10", default-features = false } +anstyle-yansi = { version = "=2.0.2", default-features = false } +approx = { version = "=0.5.1", default-features = false } +async-scoped = { version = "=0.9.0", default-features = false } +async-trait = { version = "=0.1.83", default-features = false } +aws-config = { version = "=1.5.10" } +aws-sdk-s3 = { version = "=1.65.0", default-features = false } +bitvec = { version = "=1.0.1", default-features = false } +bytes-utils = { version = "=0.1.4", default-features = false } +clap = { version = "=4.5.23", features = ["color", "error-context", "help", "std", "suggestions", "usage"] } +clap_complete = { version = "=4.5.38", default-features = false } +convert_case = { version = "=0.6.0", default-features = false } +criterion-macro = { version = "=0.4.0", default-features = false } +derive-where = { version = "=1.2.7", default-features = false, features = ["nightly"] } +derive_more = { version = "=1.0.0", default-features = false } +dotenv-flow = { version = "=0.16.2", default-features = false } +expect-test = { version = "=1.5.0", default-features = false } +frunk_core = { version = "0.4.3", default-features = false } +futures = { version = "=0.3.31", default-features = false } +hifijson = { version = "=0.2.2", default-features = false } +humansize = { version = "=2.1.3", default-features = false } +hyper = { version = "=1.5.1", default-features = false } +include_dir = { version = "=0.7.4", default-features = false } +insta = { version = "=1.41.1", default-features = false } +itertools = { version = "0.13.0", default-features = false } +jsonschema = { version = "=0.26.1", default-features = false } +justjson = { version = "=0.3.0", default-features = false } +lexical = { version = "=7.0.3", default-features = false } +libp2p = { version = "=0.54.1", default-features = false } +libp2p-stream = { version = "=0.2.0-alpha", default-features = false } +logos = { version = "=0.15.0", default-features = false } +memchr = { version = "=2.7.4", default-features = false } +mimalloc = { version = "=0.1.43", default-features = false } +mime = { version = "=0.3.17", default-features = false } +num-traits = { version = "=0.2.19", default-features = false } +once_cell = { version = "=1.20.2", default-features = false } +opentelemetry = { version = "=0.27.1", default-features = false } +opentelemetry-otlp = { version = "=0.27.0", default-features = false } +opentelemetry_sdk = { version = "=0.27.1", default-features = false } +orx-concurrent-vec = { version = "=3.0.2", default-features = false } +owo-colors = { version = "=4.1.0", default-features = false } +paste = { version = "=1.0.15", default-features = false } +pin-project = { version = "=1.1.7", default-features = false } +pin-project-lite = { version = "=0.2.15", default-features = false } +postgres-protocol = { version = "=0.6.7", default-features = false } +pretty_assertions = { version = "=1.4.1", default-features = false, features = ["alloc"] } +proc-macro-error2 = { version = "=2.0.1", default-features = false } +proc-macro2 = { version = "=1.0.92", default-features = false } +proptest = { version = "=1.5.0", default-features = false, features = ["alloc"] } +quote = { version = "=1.0.37", default-features = false } +rand = { version = "=0.8.5", default-features = false } +refinery = { version = "=0.8.14", default-features = false } +rustc_version = { version = "=0.4.1", default-features = false } +scc = { version = "=2.2.5", default-features = false } +sentry = { version = "=0.35.0", default-features = false, features = ["backtrace", "contexts", "debug-images", "panic", "reqwest", "rustls", "tower-http", "tracing"] } +seq-macro = { version = "=0.3.5", default-features = false } +serde_plain = { version = "=1.0.2", default-features = false } +serde_with = { version = "=3.11.0", default-features = false } +sha2 = { version = "=0.10.8", default-features = false } +similar-asserts = { version = "=1.6.0", default-features = false } +supports-color = { version = "=3.0.2", default-features = false } +supports-unicode = { version = "=3.0.0", default-features = false } +syn = { version = "=2.0.90", default-features = false } +tachyonix = { version = "=0.3.1", default-features = false } +tarpc = { version = "=0.35.0", default-features = false } +temporal-client = { git = "https://github.com/temporalio/sdk-core", rev = "4a2368d" } +temporal-sdk-core-protos = { git = "https://github.com/temporalio/sdk-core", rev = "4a2368d" } +test-fuzz = { version = "=7.0.1", default-features = false } +test-log = { version = "=0.2.16", default-features = false } +test-strategy = { version = "=0.4.0", default-features = false } +thiserror = { version = "=2.0.4", default-features = false } +tokio-stream = { version = "=0.1.17", default-features = false } +tokio-test = { version = "=0.4.4", default-features = false } +tower = { version = "=0.5.1", default-features = false } +tower-test = { version = "=0.4.0", default-features = false } +tracing = { version = "=0.1.41", default-features = false } +tracing-error = { version = "=0.2.1", default-features = false } +tracing-flame = { version = "=0.2.0", default-features = false } +tracing-opentelemetry = { version = "=0.28.0", default-features = false } +trait-variant = { version = "=0.1.2", default-features = false } +trybuild = { version = "=1.0.101", default-features = false } +tsify = { version = "=0.4.5", default-features = false } +unicode-ident = { version = "=1.0.14", default-features = false } +virtue = { version = "=0.0.18", default-features = false } +walkdir = { version = "=2.5.0", default-features = false } +winnow = { version = "=0.6.20", default-features = false } [profile.dev] codegen-backend = "cranelift" diff --git a/libs/@blockprotocol/type-system/rust/Cargo.toml b/libs/@blockprotocol/type-system/rust/Cargo.toml index fccf539c764..008f3866d7e 100644 --- a/libs/@blockprotocol/type-system/rust/Cargo.toml +++ b/libs/@blockprotocol/type-system/rust/Cargo.toml @@ -51,7 +51,7 @@ utoipa = ["dep:utoipa"] workspace = true [target.'cfg(target_arch = "wasm32")'.dependencies] -wasm-bindgen = { version = "0.2.92", features = ["serde-serialize"] } +wasm-bindgen = { version = "0.2.99", features = ["serde-serialize"] } # The `console_error_panic_hook` crate provides better debugging of panics by # logging them with `console.error`. This is great for development, but requires # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for @@ -59,4 +59,4 @@ wasm-bindgen = { version = "0.2.92", features = ["serde-serialize"] } console_error_panic_hook = { version = "0.1.7" } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] -wasm-bindgen-test = "0.3.42" +wasm-bindgen-test = "0.3.49" diff --git a/libs/@local/hql/diagnostics/Cargo.toml b/libs/@local/hql/diagnostics/Cargo.toml index 8c3c90d5ffb..9b278bd2da5 100644 --- a/libs/@local/hql/diagnostics/Cargo.toml +++ b/libs/@local/hql/diagnostics/Cargo.toml @@ -25,7 +25,7 @@ serde_with = { workspace = true, optional = true, features = ["std", "macros" thiserror = { workspace = true } [dev-dependencies] -jsonptr = "0.6.0" +jsonptr = "0.6.3" serde_json = { workspace = true } [features] diff --git a/libs/@local/temporal-client/Cargo.toml b/libs/@local/temporal-client/Cargo.toml index 8b6fef2d5b4..02d09512366 100644 --- a/libs/@local/temporal-client/Cargo.toml +++ b/libs/@local/temporal-client/Cargo.toml @@ -16,13 +16,13 @@ hash-graph-types = { workspace = true, public = true } error-stack = { workspace = true } # Private third-party dependencies -serde = { workspace = true } -serde_json = { workspace = true } -temporal-io-client = { workspace = true } -temporal-io-sdk-core-protos = { workspace = true } -thiserror = { workspace = true } -url = { workspace = true } -uuid = { workspace = true, features = ["v4"] } +serde = { workspace = true } +serde_json = { workspace = true } +temporal-client = { workspace = true } +temporal-sdk-core-protos = { workspace = true } +thiserror = { workspace = true } +url = { workspace = true } +uuid = { workspace = true, features = ["v4"] } [lints] workspace = true diff --git a/libs/@local/temporal-client/src/ai.rs b/libs/@local/temporal-client/src/ai.rs index 03bba3d1d82..30ba4ebb72b 100644 --- a/libs/@local/temporal-client/src/ai.rs +++ b/libs/@local/temporal-client/src/ai.rs @@ -7,8 +7,8 @@ use hash_graph_types::{ ontology::{DataTypeWithMetadata, EntityTypeWithMetadata, PropertyTypeWithMetadata}, }; use serde::Serialize; -use temporal_io_client::{WorkflowClientTrait as _, WorkflowOptions}; -use temporal_io_sdk_core_protos::{ +use temporal_client::{WorkflowClientTrait as _, WorkflowOptions}; +use temporal_sdk_core_protos::{ ENCODING_PAYLOAD_KEY, JSON_ENCODING_VAL, temporal::api::common::v1::Payload, }; use uuid::Uuid; diff --git a/libs/@local/temporal-client/src/lib.rs b/libs/@local/temporal-client/src/lib.rs index 4b9c5da3dcb..2b256e99d2a 100644 --- a/libs/@local/temporal-client/src/lib.rs +++ b/libs/@local/temporal-client/src/lib.rs @@ -6,7 +6,7 @@ mod ai; mod error; use error_stack::{Report, ResultExt as _}; -use temporal_io_client::{Client, ClientOptions, ClientOptionsBuilder, RetryClient}; +use temporal_client::{Client, ClientOptions, ClientOptionsBuilder, RetryClient}; use url::Url; #[derive(Debug)] @@ -28,7 +28,7 @@ impl IntoFuture for TemporalClientConfig { Ok(TemporalClient { client: self .options - .connect("HASH", None, None) + .connect("HASH", None) .await .change_context(ConnectionError)?, })