From 51f20a033e5769ddcd53d6921e0a1d15fdd1b114 Mon Sep 17 00:00:00 2001 From: Yohe-Am <56622350+Yohe-Am@users.noreply.github.com> Date: Sun, 25 Aug 2024 20:16:18 +0300 Subject: [PATCH] feat(metagen): client_ts (#790) - Implements `client_ts` as described in #777 . #### Migration notes ... - [x] The change comes with new or modified tests - [x] Hard-to-understand functions have explanatory comments - [ ] End-user documentation is updated to reflect the change ## Summary by CodeRabbit - **Bug Fixes** - Improved error handling in the `typegraph` function to provide better error messages. - **Chores** - Updated Docker image references to use `docker.io` prefix for consistency. - Excluded unnecessary files from the VSCode settings. - Enhanced configurability of Docker commands in development tasks. - Updated environment variable `GHJK_VERSION` to reflect a semantic versioning format. - **New Features** - Introduced modules and methods for TypeScript and Python code generation in the `metagen` library, enhancing client generation capabilities. - Added `test_typegraph_3` function for improved testing capabilities. - Included metadata for the package manager in the project settings. --- .ghjk/deno.lock | 498 +--- .ghjk/lock.json | 907 ++----- .github/workflows/publish-website.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/tests.yml | 2 +- .pre-commit-config.yaml | 4 +- .vscode/settings.json | 31 +- CONTRIBUTING.md | 2 +- Cargo.lock | 11 + Cargo.toml | 2 + dev/Dockerfile | 2 +- dev/consts.ts | 2 +- dev/cross.Dockerfile | 2 +- dev/deps.ts | 12 +- dev/envs/compose.adminer.yml | 2 +- dev/envs/compose.base.yml | 4 +- dev/envs/compose.imgproxy.yml | 2 +- dev/envs/compose.prisma.yml | 2 +- dev/tasks-dev.ts | 7 +- examples/deno.lock | 28 + examples/typegraphs/metagen/rs/mdk.rs | 14 +- ghjk.ts | 2 + libs/common/src/typegraph/mod.rs | 11 + libs/metagen/fixtures/client_rs/Cargo.toml | 16 + libs/metagen/fixtures/client_rs/main.rs | 137 + libs/metagen/fixtures/client_ts/deno.json | 1 + libs/metagen/fixtures/client_ts/main.ts | 85 + .../{tests => fixtures}/mat_rust/lib.rs | 0 libs/metagen/{tests => fixtures}/placeholder | 0 libs/metagen/{tests => fixtures}/tg.ts | 0 libs/metagen/fixtures/tg2.ts | 69 + libs/metagen/src/client_py/mod.rs | 362 +++ libs/metagen/src/client_py/node_metas.rs | 171 ++ libs/metagen/src/client_py/selections.rs | 98 + libs/metagen/src/client_py/static/client.py | 513 ++++ libs/metagen/src/client_py/types.rs | 298 +++ libs/metagen/src/client_py/utils.rs | 42 + libs/metagen/src/client_rs/mod.rs | 506 ++++ libs/metagen/src/client_rs/node_metas.rs | 241 ++ libs/metagen/src/client_rs/selections.rs | 179 ++ libs/metagen/src/client_rs/static/Cargo.toml | 14 + libs/metagen/src/client_rs/static/client.rs | 2126 +++++++++++++++ libs/metagen/src/client_rs/static/lib.rs | 4 + libs/metagen/src/client_ts/mod.rs | 379 +++ libs/metagen/src/client_ts/node_metas.rs | 183 ++ libs/metagen/src/client_ts/selections.rs | 117 + libs/metagen/src/client_ts/static/mod.ts | 639 +++++ libs/metagen/src/lib.rs | 44 +- libs/metagen/src/macros.rs | 22 + libs/metagen/src/mdk/types.rs | 295 --- libs/metagen/src/mdk_python/mod.rs | 9 +- .../src/mdk_python/static/types.py.jinja | 10 +- libs/metagen/src/mdk_python/types.rs | 2 +- libs/metagen/src/mdk_rust/mod.rs | 21 +- libs/metagen/src/mdk_rust/static/mdk.rs | 2 +- libs/metagen/src/mdk_rust/stubs.rs | 8 +- libs/metagen/src/mdk_rust/types.rs | 243 +- libs/metagen/src/mdk_typescript/mod.rs | 19 +- libs/metagen/src/mdk_typescript/types.rs | 244 +- libs/metagen/src/shared/client.rs | 168 ++ libs/metagen/src/{mdk => shared}/mod.rs | 51 + libs/metagen/src/shared/types.rs | 321 +++ libs/metagen/src/tests/fixtures.rs | 26 +- libs/metagen/src/tests/mod.rs | 14 +- libs/metagen/tests/mat_rust/Cargo.toml | 14 - libs/metagen/tests/pnpm-lock.yaml | 0 meta-lsp/package.json | 3 +- typegate/deno.lock | 191 +- typegate/engine/build.rs | 5 +- typegate/import_map.json | 2 +- .../__snapshots__/metagen_test.ts.snap | 740 +++--- typegate/tests/metagen/metagen_test.ts | 105 +- .../identities/py/handlers_types.py | 28 +- .../metagen/typegraphs/identities/rs/mdk.rs | 34 +- .../metagen/typegraphs/identities/ts/mdk.ts | 56 +- typegate/tests/metagen/typegraphs/sample.ts | 95 + .../metagen/typegraphs/sample/metatype.yml | 20 + .../metagen/typegraphs/sample/py/client.py | 701 +++++ .../metagen/typegraphs/sample/py/main.py | 110 + .../metagen/typegraphs/sample/rs/Cargo.lock | 1318 ++++++++++ .../metagen/typegraphs/sample/rs/Cargo.toml | 15 + .../metagen/typegraphs/sample/rs/client.rs | 2318 +++++++++++++++++ .../metagen/typegraphs/sample/rs/main.rs | 173 ++ .../metagen/typegraphs/sample/ts/client.ts | 796 ++++++ .../metagen/typegraphs/sample/ts/deno.json | 1 + .../metagen/typegraphs/sample/ts/main.ts | 85 + typegate/tests/metatype.yml | 2 + .../runtimes/wasm_reflected/rust/Cargo.lock | 2 +- typegate/tests/runtimes/wasm_wire/rust/mdk.rs | 6 +- typegraph/deno/sdk/jsr.json | 5 + typegraph/deno/sdk/src/typegraph.ts | 9 +- website/package.json | 3 +- whiz.yaml | 2 +- 93 files changed, 13842 insertions(+), 2227 deletions(-) create mode 100644 libs/metagen/fixtures/client_rs/Cargo.toml create mode 100644 libs/metagen/fixtures/client_rs/main.rs create mode 100644 libs/metagen/fixtures/client_ts/deno.json create mode 100644 libs/metagen/fixtures/client_ts/main.ts rename libs/metagen/{tests => fixtures}/mat_rust/lib.rs (100%) rename libs/metagen/{tests => fixtures}/placeholder (100%) rename libs/metagen/{tests => fixtures}/tg.ts (100%) create mode 100644 libs/metagen/fixtures/tg2.ts create mode 100644 libs/metagen/src/client_py/mod.rs create mode 100644 libs/metagen/src/client_py/node_metas.rs create mode 100644 libs/metagen/src/client_py/selections.rs create mode 100644 libs/metagen/src/client_py/static/client.py create mode 100644 libs/metagen/src/client_py/types.rs create mode 100644 libs/metagen/src/client_py/utils.rs create mode 100644 libs/metagen/src/client_rs/mod.rs create mode 100644 libs/metagen/src/client_rs/node_metas.rs create mode 100644 libs/metagen/src/client_rs/selections.rs create mode 100644 libs/metagen/src/client_rs/static/Cargo.toml create mode 100644 libs/metagen/src/client_rs/static/client.rs create mode 100644 libs/metagen/src/client_rs/static/lib.rs create mode 100644 libs/metagen/src/client_ts/mod.rs create mode 100644 libs/metagen/src/client_ts/node_metas.rs create mode 100644 libs/metagen/src/client_ts/selections.rs create mode 100644 libs/metagen/src/client_ts/static/mod.ts create mode 100644 libs/metagen/src/macros.rs delete mode 100644 libs/metagen/src/mdk/types.rs create mode 100644 libs/metagen/src/shared/client.rs rename libs/metagen/src/{mdk => shared}/mod.rs (51%) create mode 100644 libs/metagen/src/shared/types.rs delete mode 100644 libs/metagen/tests/mat_rust/Cargo.toml delete mode 100644 libs/metagen/tests/pnpm-lock.yaml create mode 100644 typegate/tests/metagen/typegraphs/sample.ts create mode 100644 typegate/tests/metagen/typegraphs/sample/metatype.yml create mode 100644 typegate/tests/metagen/typegraphs/sample/py/client.py create mode 100644 typegate/tests/metagen/typegraphs/sample/py/main.py create mode 100644 typegate/tests/metagen/typegraphs/sample/rs/Cargo.lock create mode 100644 typegate/tests/metagen/typegraphs/sample/rs/Cargo.toml create mode 100644 typegate/tests/metagen/typegraphs/sample/rs/client.rs create mode 100644 typegate/tests/metagen/typegraphs/sample/rs/main.rs create mode 100644 typegate/tests/metagen/typegraphs/sample/ts/client.ts create mode 100644 typegate/tests/metagen/typegraphs/sample/ts/deno.json create mode 100644 typegate/tests/metagen/typegraphs/sample/ts/main.ts diff --git a/.ghjk/deno.lock b/.ghjk/deno.lock index 6ac5771613..3ff9615ac0 100644 --- a/.ghjk/deno.lock +++ b/.ghjk/deno.lock @@ -91,15 +91,7 @@ } }, "redirects": { - "https://github.com/levibostian/deno-udd/raw/ignore-prerelease/mod.ts": "https://raw.githubusercontent.com/levibostian/deno-udd/ignore-prerelease/mod.ts", - "https://raw.github.com/metatypedev/ghjk/44d9a41/deps/common.ts": "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/deps/common.ts", - "https://raw.github.com/metatypedev/ghjk/44d9a41/mod.ts": "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/mod.ts", - "https://raw.github.com/metatypedev/ghjk/44d9a41/ports/mod.ts": "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/mod.ts", - "https://raw.github.com/metatypedev/ghjk/44d9a41/std.ts": "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/std.ts", - "https://raw.github.com/metatypedev/ghjk/8d50518/deps/common.ts": "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/deps/common.ts", - "https://raw.github.com/metatypedev/ghjk/8d50518/mod.ts": "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/mod.ts", - "https://raw.github.com/metatypedev/ghjk/8d50518/ports/mod.ts": "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/mod.ts", - "https://raw.github.com/metatypedev/ghjk/8d50518/std.ts": "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/std.ts" + "https://github.com/levibostian/deno-udd/raw/ignore-prerelease/mod.ts": "https://raw.githubusercontent.com/levibostian/deno-udd/ignore-prerelease/mod.ts" }, "remote": { "https://deno.land/std@0.116.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58", @@ -114,12 +106,6 @@ "https://deno.land/std@0.116.0/path/posix.ts": "34349174b9cd121625a2810837a82dd8b986bbaaad5ade690d1de75bbb4555b2", "https://deno.land/std@0.116.0/path/separator.ts": "8fdcf289b1b76fd726a508f57d3370ca029ae6976fcde5044007f062e643ff1c", "https://deno.land/std@0.116.0/path/win32.ts": "11549e8c6df8307a8efcfa47ad7b2a75da743eac7d4c89c9723a944661c8bd2e", - "https://deno.land/std@0.120.0/_wasm_crypto/crypto.js": "5c283a80e1059d16589b79fa026be5fb0a28424302a99487cadceef8c17f8afa", - "https://deno.land/std@0.120.0/_wasm_crypto/crypto.wasm.js": "0e6df3c18beb1187b442ec7f0a03df4d18b21212172d6b4a50ee4816404771d7", - "https://deno.land/std@0.120.0/_wasm_crypto/mod.ts": "7d02009ef3ddc953c8f90561d213e02fa0a6f3eaed9b8baf0c241c8cbeec1ed3", - "https://deno.land/std@0.120.0/crypto/mod.ts": "5760510eaa0b250f78cce81ce92d83cf8c40e9bb3c3efeedd4ef1a5bb0801ef4", - "https://deno.land/std@0.120.0/encoding/ascii85.ts": "b42b041e9c668afa356dd07ccf69a6b3ee49b9ae080fdf3b03f0ac3981f4d1e6", - "https://deno.land/std@0.120.0/encoding/base64.ts": "0b58bd6477214838bf711eef43eac21e47ba9e5c81b2ce185fe25d9ecab3ebb3", "https://deno.land/std@0.140.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74", "https://deno.land/std@0.140.0/_util/os.ts": "3b4c6e27febd119d36a416d7a97bd3b0251b77c88942c8f16ee5953ea13e2e49", "https://deno.land/std@0.140.0/bytes/bytes_list.ts": "67eb118e0b7891d2f389dad4add35856f4ad5faab46318ff99653456c23b025d", @@ -197,12 +183,6 @@ "https://deno.land/std@0.182.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1", "https://deno.land/std@0.182.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba", "https://deno.land/std@0.185.0/semver/mod.ts": "200f50cf6872212667df532fb09f0b1a33d3427a5201f75fad30a0d0c6dbcce3", - "https://deno.land/std@0.196.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", - "https://deno.land/std@0.196.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", - "https://deno.land/std@0.196.0/console/_data.json": "cf2cc9d039a192b3adbfe64627167c7e6212704c888c25c769fc8f1709e1e1b8", - "https://deno.land/std@0.196.0/console/_rle.ts": "56668d5c44f964f1b4ff93f21c9896df42d6ee4394e814db52d6d13f5bb247c7", - "https://deno.land/std@0.196.0/console/unicode_width.ts": "10661c0f2eeab802d16b8b85ed8825bbc573991bbfb6affed32dc1ff994f54f9", - "https://deno.land/std@0.196.0/fmt/colors.ts": "a7eecffdf3d1d54db890723b303847b6e0a1ab4b528ba6958b8f2e754cf1b3bc", "https://deno.land/std@0.213.0/archive/_common.ts": "85edd5cdd4324833f613c1bc055f8e2f935cc9229c6b3044421268d9959997ef", "https://deno.land/std@0.213.0/archive/untar.ts": "7677c136f2188cd8c33363ccaaee6e77d4ca656cca3e2093d08de8f294d4353d", "https://deno.land/std@0.213.0/assert/assert.ts": "bec068b2fccdd434c138a555b19a2c2393b71dfaada02b7d568a01541e67cdc5", @@ -628,70 +608,6 @@ "https://deno.land/std@0.221.0/fmt/colors.ts": "d239d84620b921ea520125d778947881f62c50e78deef2657073840b8af9559a", "https://deno.land/std@0.221.0/text/closest_string.ts": "8a91ee8b6d69ff96addcb7c251dad53b476ac8be9c756a0ef786abe9e13a93a5", "https://deno.land/std@0.221.0/text/levenshtein_distance.ts": "24be5cc88326bbba83ca7c1ea89259af0050cffda2817ff3a6d240ad6495eae2", - "https://deno.land/std@0.76.0/encoding/base64.ts": "b1d8f99b778981548457ec74bc6273ad785ffd6f61b2233bd5b30925345b565d", - "https://deno.land/std@0.76.0/encoding/hex.ts": "07a03ba41c96060a4ed4ba272e50b9e23f3c5b3839f4b069cdebc24d57434386", - "https://deno.land/std@0.76.0/hash/_wasm/hash.ts": "005f64c4d9343ecbc91e0da9ae5e800f146c20930ad829bbb872c5c06bd89c5f", - "https://deno.land/std@0.76.0/hash/_wasm/wasm.js": "5ac48aa0c3931d7f31dba628be5ab0aa4e786354197eb4d7d0583f9b50be1397", - "https://deno.land/std@0.76.0/hash/mod.ts": "e764a6a9ab2f5519a97f928e17cc13d984e3dd5c7f742ff9c1c8fb3114790f0c", - "https://deno.land/x/cliffy@v1.0.0-rc.3/_utils/distance.ts": "02af166952c7c358ac83beae397aa2fbca4ad630aecfcd38d92edb1ea429f004", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/_argument_types.ts": "ab269dacea2030f865a07c2a1e953ec437a64419a05bad1f1ddaab3f99752ead", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/_errors.ts": "12d513ff401020287a344e0830e1297ce1c80c077ecb91e0ac5db44d04a6019c", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/_spread.ts": "0cc6eb70a6df97b5d7d26008822d39f3e8a1232ee0a27f395aa19e68de738245", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/_type_utils.ts": "820004a59bc858e355b11f80e5b3ff1be2c87e66f31f53f253610170795602f0", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/_utils.ts": "3c88ff4f36eba298beb07de08068fdce5e5cb7b9d82c8a319f09596d8279be64", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/command.ts": "ae690745759524082776b7f271f66d5b93933170b1b132f888bd4ac12e9fdd7d", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/_bash_completions_generator.ts": "0c6cb1df4d378d22f001155781d97a9c3519fd10c48187a198fef2cc63b0f84a", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/_fish_completions_generator.ts": "8ba4455f7f76a756e05c3db4ce35332b2951af65a2891f2750b530e06880f495", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/_zsh_completions_generator.ts": "c74525feaf570fe8c14433c30d192622c25603f1fc64694ef69f2a218b41f230", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/bash.ts": "53fe78994eb2359110dc4fa79235bdd86800a38c1d6b1c4fe673c81756f3a0e2", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/complete.ts": "58df61caa5e6220ff2768636a69337923ad9d4b8c1932aeb27165081c4d07d8b", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/completions_command.ts": "506f97f1c6b0b1c3e9956e5069070028b818942310600d4157f64c9b644d3c49", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/fish.ts": "6f0b44b4067740b2931c9ec8863b6619b1d3410fea0c5a3988525a4c53059197", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/mod.ts": "8dda715ca25f3f66d5ec232b76d7c9a96dd4c64b5029feff91738cc0c9586fb1", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/completions/zsh.ts": "f1263c3946975e090d4aadc8681db811d86b52a8ae680f246e03248025885c21", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/deprecated.ts": "bbe6670f1d645b773d04b725b8b8e7814c862c9f1afba460c4d599ffe9d4983c", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/deps.ts": "7473ebd5625bf901becd7ff80afdde3b8a50ae5d1bbfa2f43805cfacf4559d5a", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/help/_help_generator.ts": "532dd4a928baab8b45ce46bb6d20e2ebacfdf3da141ce9d12da796652b1de478", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/help/help_command.ts": "fbbf0c0827dd21d3cec7bcc68c00c20b55f53e2b621032891b9d23ac4191231c", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/help/mod.ts": "8369b292761dcc9ddaf41f2d34bfb06fb6800b69efe80da4fc9752c3b890275b", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/mod.ts": "4b708df1b97152522bee0e3828f06abbbc1d2250168910e5cf454950d7b7404b", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/type.ts": "f588f5d9635b79100044e62aced4b00e510e75b83801f9b089c40c2d98674de2", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types.ts": "bc9ff7459b9cc1079eeb95ff101690a51b4b4afa4af5623340076ee361d08dbb", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/action_list.ts": "33c98d449617c7a563a535c9ceb3741bde9f6363353fd492f90a74570c611c27", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/boolean.ts": "3879ec16092b4b5b1a0acb8675f8c9250c0b8a972e1e4c7adfba8335bd2263ed", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/child_command.ts": "f1fca390c7fbfa7a713ca15ef55c2c7656bcbb394d50e8ef54085bdf6dc22559", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/command.ts": "325d0382e383b725fd8d0ef34ebaeae082c5b76a1f6f2e843fee5dbb1a4fe3ac", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/enum.ts": "8a7cd2898e03089234083bb78c8b1d9b7172254c53c32d4710321638165a48ec", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/file.ts": "8618f16ac9015c8589cbd946b3de1988cc4899b90ea251f3325c93c46745140e", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/integer.ts": "29864725fd48738579d18123d7ee78fed37515e6dc62146c7544c98a82f1778d", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/number.ts": "aeba96e6f470309317a16b308c82e0e4138a830ec79c9877e4622c682012bc1f", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/types/string.ts": "e4dadb08a11795474871c7967beab954593813bb53d9f69ea5f9b734e43dc0e0", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/_check_version.ts": "6cfa7dc26bc0dc46381500e8d4b130fb224f4c5456152dada15bd3793edca89b", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/mod.ts": "4eff69c489467be17dea27fb95a795396111ee385d170ac0cbcc82f0ea38156c", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/provider.ts": "c23253334097dc4b8a147ccdeb3aa44f5a95aa953a6386cb5396f830d95d77a5", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/provider/deno_land.ts": "24f8d82e38c51e09be989f30f8ad21f9dd41ac1bb1973b443a13883e8ba06d6d", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/provider/github.ts": "99e1b133dd446c6aa79f69e69c46eb8bc1c968dd331c2a7d4064514a317c7b59", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/provider/nest_land.ts": "0e07936cea04fa41ac9297f32d87f39152ea873970c54cb5b4934b12fee1885e", - "https://deno.land/x/cliffy@v1.0.0-rc.3/command/upgrade/upgrade_command.ts": "3640a287d914190241ea1e636774b1b4b0e1828fa75119971dd5304784061e05", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/_errors.ts": "f1fbb6bfa009e7950508c9d491cfb4a5551027d9f453389606adb3f2327d048f", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/_utils.ts": "340d3ecab43cde9489187e1f176504d2c58485df6652d1cdd907c0e9c3ce4cc2", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/_validate_flags.ts": "e60b9038c0136ab7e6bd1baf0e993a07bf23f18afbfb6e12c59adf665a622957", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/deprecated.ts": "a72a35de3cc7314e5ebea605ca23d08385b218ef171c32a3f135fb4318b08126", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/flags.ts": "3e62c4a9756b5705aada29e7e94847001356b3a83cd18ad56f4207387a71cf51", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/types.ts": "9e2f75edff2217d972fc711a21676a59dfd88378da2f1ace440ea84c07db1dcc", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/types/boolean.ts": "4c026dd66ec9c5436860dc6d0241427bdb8d8e07337ad71b33c08193428a2236", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/types/integer.ts": "b60d4d590f309ddddf066782d43e4dc3799f0e7d08e5ede7dc62a5ee94b9a6d9", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/types/number.ts": "610936e2d29de7c8c304b65489a75ebae17b005c6122c24e791fbed12444d51e", - "https://deno.land/x/cliffy@v1.0.0-rc.3/flags/types/string.ts": "e89b6a5ce322f65a894edecdc48b44956ec246a1d881f03e97bbda90dd8638c5", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/_layout.ts": "e4a518da28333de95ad791208b9930025987c8b93d5f8b7f30b377b3e26b24e1", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/_utils.ts": "fd48d1a524a42e72aa3ad2eec858a92f5a00728d306c7e8436fba6c34314fee6", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/border.ts": "5c6e9ef5078c6930169aacb668b274bdbb498461c724a7693ac9270fe9d3f5d5", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/cell.ts": "1ffabd43b6b7fddfac9625cb0d015532e144702a9bfed03b358b79375115d06b", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/column.ts": "cf14009f2cb14bad156f879946186c1893acdc6a2fee6845db152edddb6a2714", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/consume_words.ts": "456e75755fdf6966abdefb8b783df2855e2a8bad6ddbdf21bd748547c5fc1d4b", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/deps.ts": "1226c4d39d53edc81d7c3e661fb8a79f2e704937c276c60355cd4947a0fe9153", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/row.ts": "79eb1468aafdd951e5963898cdafe0752d4ab4c519d5f847f3d8ecb8fe857d4f", - "https://deno.land/x/cliffy@v1.0.0-rc.3/table/table.ts": "298671e72e61f1ab18b42ae36643181993f79e29b39dc411fdc6ffd53aa04684", "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_argument_types.ts": "ab269dacea2030f865a07c2a1e953ec437a64419a05bad1f1ddaab3f99752ead", "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_errors.ts": "d78e1b4d69d84b8b476b5f3c0b028e3906d48f21b8f1ca1d36d5abe9ccfe48bc", "https://deno.land/x/cliffy@v1.0.0-rc.4/command/_spread.ts": "0cc6eb70a6df97b5d7d26008822d39f3e8a1232ee0a27f395aa19e68de738245", @@ -756,43 +672,6 @@ "https://deno.land/x/convert_bytes@v2.1.1/mod.ts": "036bd2d9519c8ad44bd5a15d4e42123dc16843f793b3c81ca1fca905b21dd7df", "https://deno.land/x/convert_bytes@v2.1.1/src/utility.ts": "a94b4c50286910a23a90c0a0510e8191fa3311dec44d062a6d4fe3d5b7ff8176", "https://deno.land/x/ctrlc@0.2.1/mod.ts": "b7895894c596f2e8355d3b181ed30fa74cb54b94c419b730f6cb6ff96b20ec8b", - "https://deno.land/x/dax@0.38.0/mod.ts": "3a5d7e6ac12547feec5d3c0c96717f14276891a3802fbbc73e5901e4f20eb08d", - "https://deno.land/x/dax@0.38.0/src/command.ts": "f20135ef7188a0fc9f773d50e88775dee8653044a7f536fb2fb885b293c26ec4", - "https://deno.land/x/dax@0.38.0/src/command_handler.ts": "a9e40f0f1ec57318e62904b5785fede82dcdf1101922ebb3ebfad8f1c4d9c8df", - "https://deno.land/x/dax@0.38.0/src/commands/args.ts": "a138aef24294e3cbf13cef08f4836d018e8dd99fd06ad82e7e7f08ef680bbc1d", - "https://deno.land/x/dax@0.38.0/src/commands/cat.ts": "a136e9fe729d6b89c9bab469e6367f557bcddf3a4a3240b2ac280ff6da540b88", - "https://deno.land/x/dax@0.38.0/src/commands/cd.ts": "3d70605c6f8606008072f52763dbf4a979fa501975d006cf7f50eed0576936ab", - "https://deno.land/x/dax@0.38.0/src/commands/cp_mv.ts": "d57102f05f8eb6fb8f705e532a0e01c0dc7ba960c1e0828d4a5bef7ff411215f", - "https://deno.land/x/dax@0.38.0/src/commands/echo.ts": "8ca19f63779f8fa9cf2a29e21bdb31cfd6a3a09a820e5a83d6244325dea5f360", - "https://deno.land/x/dax@0.38.0/src/commands/exit.ts": "ef83eefb99270872ac679e38cee9aec345da9a345a3873fe6660f05aa577f937", - "https://deno.land/x/dax@0.38.0/src/commands/export.ts": "c10d1dc6a45fd00e40afa6b19d7ecd29d09333f422b5b0fc75863baf13350969", - "https://deno.land/x/dax@0.38.0/src/commands/mkdir.ts": "828a2d356fcff05d022f0e5ef76ed4a899b5370485fa4144fe378040a0f05aef", - "https://deno.land/x/dax@0.38.0/src/commands/printenv.ts": "4fc09ecf88e35bc9d810e3f45d1d8e808613e73701466ca6e48fca8d1810a48a", - "https://deno.land/x/dax@0.38.0/src/commands/pwd.ts": "6507d70bf02026bde8f58da166c37cdc2f7e1fda807b003f096aab077b866ee5", - "https://deno.land/x/dax@0.38.0/src/commands/rm.ts": "43ef496c34b722d007b945232d51273fcc6d7315f6198f6a6291bb7151941426", - "https://deno.land/x/dax@0.38.0/src/commands/sleep.ts": "413bacfd3bebf2a1397cda223776baadef8596f40558d6c2686ffd9b6ad80e54", - "https://deno.land/x/dax@0.38.0/src/commands/test.ts": "b0f56b3d1d038b47fe826bb3dab056746aefe12df6222e29150e7e6f78a51d9c", - "https://deno.land/x/dax@0.38.0/src/commands/touch.ts": "40a0292e5e4f35c057ac50445a124703355d2955a25b53a223aebf0b3b016e4e", - "https://deno.land/x/dax@0.38.0/src/commands/unset.ts": "1ffec8b32bbac8ef7b90b2ba1fc4d9339d3563ef8e302b14d119f9c220564985", - "https://deno.land/x/dax@0.38.0/src/common.ts": "37449926d3bc874aac4e4ff4ea06d46251dc54ad0bbb5721c7eb4920e2d5b591", - "https://deno.land/x/dax@0.38.0/src/console/confirm.ts": "d9128d10b77fcc0a8df2784f71c79df68f5c8e00a34b04547b9ba9ddf1c97f96", - "https://deno.land/x/dax@0.38.0/src/console/logger.ts": "e0ab5025915cef70df03681c756e211f25bb2e4331f82ed4256b17ddd9e794ea", - "https://deno.land/x/dax@0.38.0/src/console/mod.ts": "de8af7d646f6cb222eee6560171993690247941b13ed9d757789d16f019d73ee", - "https://deno.land/x/dax@0.38.0/src/console/multiSelect.ts": "31003744e58f45f720271bd034d8cfba1055c954ba02d77a2f2eb21e4c1ed55a", - "https://deno.land/x/dax@0.38.0/src/console/progress/format.ts": "15ddbb8051580f88ed499281e12ca6f881f875ab73268d7451d7113ee130bd7d", - "https://deno.land/x/dax@0.38.0/src/console/progress/interval.ts": "80188d980a27c2eb07c31324365118af549641442f0752fe7c3b0c91832e5046", - "https://deno.land/x/dax@0.38.0/src/console/progress/mod.ts": "dd9330c3edd1790d70808d043f417f0eaf80a4442a945545c38e47ce11e907b6", - "https://deno.land/x/dax@0.38.0/src/console/prompt.ts": "1ad65c8a5a27fb58ce6138f8ebefe2fca4cd12015fea550fbdc62f875d4b31f7", - "https://deno.land/x/dax@0.38.0/src/console/select.ts": "c9d7124d975bf34d52ea1ac88fd610ed39db8ee6505b9bb53f371cef2f56c6ab", - "https://deno.land/x/dax@0.38.0/src/console/utils.ts": "24b840d4e55eba0d5b2f79337d2940d5f9456d4d6836f35316e6495b7cb827b4", - "https://deno.land/x/dax@0.38.0/src/deps.ts": "c1e16434a805285d27c30c70a825473f88117dfa7e1d308408db1b1ab4fe743f", - "https://deno.land/x/dax@0.38.0/src/lib/mod.ts": "c992db99c8259ae3bf2d35666585dfefda84cf7cf4e624e42ea2ac7367900fe0", - "https://deno.land/x/dax@0.38.0/src/lib/rs_lib.generated.js": "0a1a482c4387379106ef0da69534ebc5b0c2a1ec9f6dab76833fe84a7e6bbdf6", - "https://deno.land/x/dax@0.38.0/src/path.ts": "451589cc3ad49cab084c50ad0ec07f7e2492a20d2f0ee7cfd80ab36360e6aa55", - "https://deno.land/x/dax@0.38.0/src/pipes.ts": "bbfc7d6bf0f0bfc363daa2f4d3c5ebf17025d82c4114d5b0ea444cf69d805670", - "https://deno.land/x/dax@0.38.0/src/request.ts": "461e16f53367c73c0ec16091c2fd6cb97a219f6af07a3a2a10029139bf404879", - "https://deno.land/x/dax@0.38.0/src/result.ts": "719a9b4bc6bafeec785106744381cd5f37927c973334fcba6a33b6418fb9e7be", - "https://deno.land/x/dax@0.38.0/src/shell.ts": "9b59a63de62003a0575f9c3300b5fff83cd7e5487582eceaa5f071a684d75e0e", "https://deno.land/x/deep_eql@v5.0.1/index.js": "60e1547b99d4ae08df387067c2ac0a1b9ab42f212f0d8a11b8b0b61270d2b1c4", "https://deno.land/x/deno_cache@0.5.2/auth_tokens.ts": "5d1d56474c54a9d152e44d43ea17c2e6a398dd1e9682c69811a313567c01ee1e", "https://deno.land/x/deno_cache@0.5.2/cache.ts": "92ce8511e1e5c00fdf53a41619aa77d632ea8e0fc711324322e4d5ebf8133911", @@ -828,15 +707,7 @@ "https://deno.land/x/foras@v2.1.4/wasm/pkg/foras.wasm.js": "2df8522df7243b0f05b1d188e220629cd5d2c92080a5f1407e15396fc35bebb3", "https://deno.land/x/fuse@v6.4.1/dist/fuse.esm.min.js": "02034c3e0a1d7f4d207ecd104c9cde1bab116d474dc60c2a019e9d4353455ff4", "https://deno.land/x/json_hash@0.2.0/canon.ts": "ce7c07abd871cd7f0eb1280ad9f58f6382f02f84a217898ce977cf35ad315877", - "https://deno.land/x/json_hash@0.2.0/crypto.ts": "8738b601a0cf52c0ff58242707e2d5f7f5ff8f7ca4d51d0282ad3b0bb56548cf", - "https://deno.land/x/json_hash@0.2.0/digest.ts": "95e3d996377eebebb960ad2b6e4fdd70d71543378a651c31de75f1e86b637fc7", - "https://deno.land/x/json_hash@0.2.0/hex.ts": "104154a6408c6b5b36ff35361011aeb3047941bd5a652724f5aebeeb89fcf9a8", - "https://deno.land/x/json_hash@0.2.0/merkle.ts": "cf48004b45fdf0412afd48fea0ba8bb16bf78f717a66a5ff505f6400a88c08cf", - "https://deno.land/x/json_hash@0.2.0/mod.ts": "b0fdd79a540d3fc6aa3e0a9a93fe6735b1a174d9ba2aba103e4a18ee4872acad", "https://deno.land/x/jszip@0.11.0/mod.ts": "5661ddc18e9ac9c07e3c5d2483bc912a7022b6af0d784bb7b05035973e640ba1", - "https://deno.land/x/object_hash@2.0.3/index.ts": "74b20a0065dc0066c60510174626db1d18e53ec966edb6f76fa33a67aa0c44e3", - "https://deno.land/x/object_hash@2.0.3/mod.ts": "648559bcafb54b930d4b6a283cc2eef20afa54de471371a97c2ccf8116941148", - "https://deno.land/x/outdent@v0.8.0/src/index.ts": "6dc3df4108d5d6fedcdb974844d321037ca81eaaa16be6073235ff3268841a22", "https://deno.land/x/ts_morph@18.0.0/bootstrap/mod.ts": "b53aad517f106c4079971fcd4a81ab79fadc40b50061a3ab2b741a09119d51e9", "https://deno.land/x/ts_morph@18.0.0/bootstrap/ts_morph_bootstrap.js": "6645ac03c5e6687dfa8c78109dc5df0250b811ecb3aea2d97c504c35e8401c06", "https://deno.land/x/ts_morph@18.0.0/common/DenoRuntime.ts": "6a7180f0c6e90dcf23ccffc86aa8271c20b1c4f34c570588d08a45880b7e172d", @@ -845,20 +716,6 @@ "https://deno.land/x/ts_morph@18.0.0/common/typescript.js": "d5c598b6a2db2202d0428fca5fd79fc9a301a71880831a805d778797d2413c59", "https://deno.land/x/wasmbuild@0.15.0/cache.ts": "89eea5f3ce6035a1164b3e655c95f21300498920575ade23161421f5b01967f4", "https://deno.land/x/wasmbuild@0.15.0/loader.ts": "d98d195a715f823151cbc8baa3f32127337628379a02d9eb2a3c5902dbccfc02", - "https://deno.land/x/which@0.3.0/mod.ts": "3e10d07953c14e4ddc809742a3447cef14202cdfe9be6678a1dfc8769c4487e6", - "https://deno.land/x/zod@v3.22.4/ZodError.ts": "4de18ff525e75a0315f2c12066b77b5c2ae18c7c15ef7df7e165d63536fdf2ea", - "https://deno.land/x/zod@v3.22.4/errors.ts": "5285922d2be9700cc0c70c95e4858952b07ae193aa0224be3cbd5cd5567eabef", - "https://deno.land/x/zod@v3.22.4/external.ts": "a6cfbd61e9e097d5f42f8a7ed6f92f93f51ff927d29c9fbaec04f03cbce130fe", - "https://deno.land/x/zod@v3.22.4/helpers/enumUtil.ts": "54efc393cc9860e687d8b81ff52e980def00fa67377ad0bf8b3104f8a5bf698c", - "https://deno.land/x/zod@v3.22.4/helpers/errorUtil.ts": "7a77328240be7b847af6de9189963bd9f79cab32bbc61502a9db4fe6683e2ea7", - "https://deno.land/x/zod@v3.22.4/helpers/parseUtil.ts": "f791e6e65a0340d85ad37d26cd7a3ba67126cd9957eac2b7163162155283abb1", - "https://deno.land/x/zod@v3.22.4/helpers/partialUtil.ts": "998c2fe79795257d4d1cf10361e74492f3b7d852f61057c7c08ac0a46488b7e7", - "https://deno.land/x/zod@v3.22.4/helpers/typeAliases.ts": "0fda31a063c6736fc3cf9090dd94865c811dfff4f3cb8707b932bf937c6f2c3e", - "https://deno.land/x/zod@v3.22.4/helpers/util.ts": "8baf19b19b2fca8424380367b90364b32503b6b71780269a6e3e67700bb02774", - "https://deno.land/x/zod@v3.22.4/index.ts": "d27aabd973613985574bc31f39e45cb5d856aa122ef094a9f38a463b8ef1a268", - "https://deno.land/x/zod@v3.22.4/locales/en.ts": "a7a25cd23563ccb5e0eed214d9b31846305ddbcdb9c5c8f508b108943366ab4c", - "https://deno.land/x/zod@v3.22.4/mod.ts": "64e55237cb4410e17d968cd08975566059f27638ebb0b86048031b987ba251c4", - "https://deno.land/x/zod@v3.22.4/types.ts": "724185522fafe43ee56a52333958764c8c8cd6ad4effa27b42651df873fc151e", "https://esm.sh/jszip@3.7.1": "f3872a819b015715edb05f81d973b5cd05d3d213d8eb28293ca5471fe7a71773", "https://esm.sh/v135/jszip@3.7.1/denonext/jszip.mjs": "d31d7f9e0de9c6db3c07ca93f7301b756273d4dccb41b600461978fc313504c9", "https://raw.githubusercontent.com/levibostian/deno-udd/ignore-prerelease/deps.ts": "2b20d8c142749898e0ad5e4adfdc554dbe1411e8e5ef093687767650a1073ff8", @@ -867,279 +724,84 @@ "https://raw.githubusercontent.com/levibostian/deno-udd/ignore-prerelease/registry.ts": "fd8e1b05f14cb988fee7a72a51e68131a920f7d4b72f949d9b86794b3c699671", "https://raw.githubusercontent.com/levibostian/deno-udd/ignore-prerelease/search.ts": "52f9a539ca76893c47d01f8c6d401487ea286d54d1305b079b8727598e4c847a", "https://raw.githubusercontent.com/levibostian/deno-udd/ignore-prerelease/semver.ts": "c051a906405dd72b55434eb0f390f678881379d57847abe4ec60d8a02af4f6f2", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/deps/cli.ts": "aac025f9372ad413b9c2663dc7f61affd597820d9448f010a510d541df3b56ea", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/deps/common.ts": "f775710b66a9099b98651cd3831906466e9b83ef98f2e5c080fd59ee801c28d4", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/deps/ports.ts": "3c60d1f7ab626ffdd81b37f4e83a780910936480da8fe24f4ccceaefa207d339", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/files/deno/mod.ts": "1b8204c3df18b908408b2148b48af788e669d0debbeb8ba119418ab1ddf1ab8f", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/files/deno/worker.ts": "8ded400d70a0bd40e281ceb1ffcdc82578443caf9c481b9eee77166472784282", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/host/mod.ts": "cc25d1f82e54e6a27eef4571145c3f34c4c8ad9148b3aa48bd3b53d1e078d95d", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/host/types.ts": "f450d9b9c0eced2650262d02455aa6f794de0edd6b052aade256882148e5697f", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/install/mod.ts": "aa54eb3e119f28d33e61645c89669da292ee00376068ead8f45be2807e7a9989", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/install/utils.ts": "d4634d4fc0e963f540402b4ca7eb5dcba340eaa0d8fceb43af57d722ad267115", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/main.ts": "ecd5e83be2d8f351058ad44424cad1f36dd2e3d76f6e8409afc47682a9eff01a", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/modules/envs/inter.ts": "84805fa208754a08f185dca7a5236de3760bbc1d0df96af86ea5fd7778f827a2", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/modules/envs/mod.ts": "5f37b9f155808f8d6d51e1f16f58c07914d8c7d8070bc5c2fb5076ab748798a7", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/modules/envs/posix.ts": "09e410e3fea9c303a5148ff2a22697474320442b9fea0bd3fc932d6828fe820f", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/modules/envs/reducer.ts": "50517084caaf73ce6618141ee4d97795060a0d3169651da7abd7251a3204465a", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/modules/envs/types.ts": "ab9715cf02e9d73f553ae757db347863be23e1e9daf94d18aab716fc27b3dbc1", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/modules/mod.ts": "fc1cb9176c6557b44ae9c6536fa51c6c4f80ac01fc476d15b0a217e70cb0d176", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/modules/ports/ambient.ts": "823ec8d98702a60e6bfcdbeb64b69dc9f5039e73a1f10e87cd51210c1aaf52d5", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/modules/ports/base.ts": "8ef8a8de372420bddcd63a1b363937f43d898059e99478a58621e8432bcd5891", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/modules/ports/db.ts": "a309d1058f66079a481141c3f1733d928b9af8a37b7ce911b1228f70fd24df0f", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/modules/ports/ghrel.ts": "ebbc30a5c31244131d937eadca73fbc099c9e7bdf0ad4f668766d4388ede143c", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/modules/ports/inter.ts": "b3999e73d73d7f928a8de86e5e2261fe6b1450ceedfb54f24537bf0803532ed0", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/modules/ports/mod.ts": "78db7040e724f84c95b1a0fdeaf0cfc53382482e8905cd352189756b953556cc", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/modules/ports/reducers.ts": "d04e813652101f67f946242df68429ed5540e499fbdb7776b8be5703f16754c8", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/modules/ports/sync.ts": "a7a297f6b098360d56af168692f3cff96f8ceeb5189e5baa249e094f8d9c42ef", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/modules/ports/types.ts": "f4dbd1a3f4b7f539b3a85418617d25adbf710b54144161880d48f6c4ec032eee", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/modules/ports/types/platform.ts": "0ecffeda71919293f9ffdb6c564ddea4f23bc85c4e640b08ea78225d34387fdc", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/modules/ports/utils.ts": "6b14b331cce66bd46e7aec51f02424327d819150f16d3f72a6b0aaf7aee43c09", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/modules/ports/worker.ts": "6b76ba1efb2e47a82582fc48bcc6264fe153a166beffccde1a9a3a185024c337", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/modules/std.ts": "419d6b04680f73f7b252257ab287d68c1571cee4347301c53278e2b53df21c4a", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/modules/tasks/deno.ts": "2b9f33253ac1257eb79a4981cd221509aa9ecf8a3c36d7bd8be1cd6c1150100b", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/modules/tasks/exec.ts": "6adcfe13f8d2da5d65331fd1601d4f950d9fc6f164bc9592204e5b08c23c5c30", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/modules/tasks/inter.ts": "63e8f2860f7e3b4d95b6f61ca56aeb8567e4f265aa9c22cace6c8075edd6210f", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/modules/tasks/mod.ts": "334b18d7c110cc05483be96353e342425c0033b7410c271a8a47d2b18308c73e", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/modules/tasks/types.ts": "072a34bd0749428bad4d612cc86abe463d4d4f74dc56cf0a48a1f41650e2399b", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/modules/types.ts": "c0f212b686a2721d076e9aeb127596c7cbc939758e2cc32fd1d165a8fb320a87", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/port.ts": "c039a010dee7dfd978478cf4c5e2256c643135e10f33c30a09f8db9915e9d89d", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/setup_logger.ts": "f8a206bda0595497d6f4718032d4a959000b32ef3346d4b507777eec6a169458", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/utils/logger.ts": "fcbafb35ae4b812412b9b301ce6d06b8b9798f94ebebe3f92677e25e4b19af3c", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/utils/mod.ts": "25bfdd222d6afec5b3f0a7e647e3d9b12abed6d222b49a4b2e95c6bbe266f533", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/utils/unarchive.ts": "f6d0e9e75f470eeef5aecd0089169f4350fc30ebfdc05466bb7b30042294d6d3", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/utils/url.ts": "e1ada6fd30fc796b8918c88456ea1b5bbd87a07d0a0538b092b91fd2bb9b7623", - "https://raw.githubusercontent.com/metatypedev/ghjk/0.2.0/utils/worker.ts": "ac4caf72a36d2e4af4f4e92f2e0a95f9fc2324b568640f24c7c2ff6dc0c11d62", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/deps/cli.ts": "aac025f9372ad413b9c2663dc7f61affd597820d9448f010a510d541df3b56ea", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/deps/common.ts": "f775710b66a9099b98651cd3831906466e9b83ef98f2e5c080fd59ee801c28d4", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/deps/ports.ts": "3c60d1f7ab626ffdd81b37f4e83a780910936480da8fe24f4ccceaefa207d339", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/files/mod.ts": "a934ddb4803896e27b40644fe75623d584e01f30bbe1e16eb26bd772aa5b6064", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/host/types.ts": "f450d9b9c0eced2650262d02455aa6f794de0edd6b052aade256882148e5697f", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/install/mod.ts": "f78083efd15e82c8cc302dd801565f39c947497cfaa039fde1023f7e0d5ab368", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/install/utils.ts": "d4634d4fc0e963f540402b4ca7eb5dcba340eaa0d8fceb43af57d722ad267115", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/mod.ts": "0fd112d8c82d47da13d00e78d1e9f22241e992ff50c5610c09f53de4e2b23608", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/modules/envs/inter.ts": "84805fa208754a08f185dca7a5236de3760bbc1d0df96af86ea5fd7778f827a2", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/modules/envs/mod.ts": "fc05e0d0f8adc1148708bedadef70fa681165fc7c5c4c641bfabb5ab03754ed6", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/modules/envs/posix.ts": "09e410e3fea9c303a5148ff2a22697474320442b9fea0bd3fc932d6828fe820f", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/modules/envs/reducer.ts": "50517084caaf73ce6618141ee4d97795060a0d3169651da7abd7251a3204465a", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/modules/envs/types.ts": "ab9715cf02e9d73f553ae757db347863be23e1e9daf94d18aab716fc27b3dbc1", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/modules/mod.ts": "fc1cb9176c6557b44ae9c6536fa51c6c4f80ac01fc476d15b0a217e70cb0d176", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/modules/ports/ambient.ts": "823ec8d98702a60e6bfcdbeb64b69dc9f5039e73a1f10e87cd51210c1aaf52d5", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/modules/ports/base.ts": "8ef8a8de372420bddcd63a1b363937f43d898059e99478a58621e8432bcd5891", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/modules/ports/db.ts": "a309d1058f66079a481141c3f1733d928b9af8a37b7ce911b1228f70fd24df0f", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/modules/ports/ghrel.ts": "a1bf0e244080b8b2a62093f536bb7eff0b5a9c596f7eef9f516c11a80aad0be1", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/modules/ports/inter.ts": "b3999e73d73d7f928a8de86e5e2261fe6b1450ceedfb54f24537bf0803532ed0", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/modules/ports/mod.ts": "78db7040e724f84c95b1a0fdeaf0cfc53382482e8905cd352189756b953556cc", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/modules/ports/reducers.ts": "d04e813652101f67f946242df68429ed5540e499fbdb7776b8be5703f16754c8", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/modules/ports/std.ts": "985902519aafef6e8e6aecc8922e70abdea5b8e97d5439bf94338b93242fe11f", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/modules/ports/sync.ts": "a7a297f6b098360d56af168692f3cff96f8ceeb5189e5baa249e094f8d9c42ef", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/modules/ports/types.ts": "f4dbd1a3f4b7f539b3a85418617d25adbf710b54144161880d48f6c4ec032eee", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/modules/ports/types/platform.ts": "0ecffeda71919293f9ffdb6c564ddea4f23bc85c4e640b08ea78225d34387fdc", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/modules/ports/utils.ts": "6b14b331cce66bd46e7aec51f02424327d819150f16d3f72a6b0aaf7aee43c09", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/modules/ports/worker.ts": "6b76ba1efb2e47a82582fc48bcc6264fe153a166beffccde1a9a3a185024c337", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/modules/std.ts": "419d6b04680f73f7b252257ab287d68c1571cee4347301c53278e2b53df21c4a", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/modules/tasks/deno.ts": "2b9f33253ac1257eb79a4981cd221509aa9ecf8a3c36d7bd8be1cd6c1150100b", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/modules/tasks/exec.ts": "2ee48e0bd0ff3c7245793e0ee6f4e3afee0b82f6375e6683db45aea6de8f45b0", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/modules/tasks/inter.ts": "63e8f2860f7e3b4d95b6f61ca56aeb8567e4f265aa9c22cace6c8075edd6210f", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/modules/tasks/mod.ts": "334b18d7c110cc05483be96353e342425c0033b7410c271a8a47d2b18308c73e", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/modules/tasks/types.ts": "072a34bd0749428bad4d612cc86abe463d4d4f74dc56cf0a48a1f41650e2399b", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/modules/types.ts": "c0f212b686a2721d076e9aeb127596c7cbc939758e2cc32fd1d165a8fb320a87", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/port.ts": "c039a010dee7dfd978478cf4c5e2256c643135e10f33c30a09f8db9915e9d89d", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/act.ts": "2ce6b8fddf61db12ba69b7cad6985237a2962ca79853edbddee5bfb49c47d1ab", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/asdf.ts": "11d41bcad5981e014478343270f05bac265990e801c525e3288113d89bd287be", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/asdf_plugin_git.ts": "a3742fcd994ded231febf33754b087ab56393a799297b26315c2cf8a388a7f82", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/cargo-binstall.ts": "72860580e6f6db9ec7ba74dbe391ad98ed49b4ff43247661b27701f4e683d41b", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/cargobi.ts": "2bf25e20c19acf7badbaa01817263f9924dd3c31b0aaa934a8b6769e235f8a57", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/cpy_bs.ts": "4066e5eb094e72be4dec2428fb7f99231dcc5c4e2db7b5ea2373a9ad9ce28662", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/curl.ts": "58acd2a158187f448d940f45bfcd87c9b4884db127dcbaaaef27258bb4ebce92", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/deno_ghrel.ts": "eca02a93ceb62ad9fb7f395361d32da0d5657aba5f7856c8ae0109135da0e070", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/earthly.ts": "7a3c8cae1631f670105a63bc41c47a49da6fc777968c0e9546c55d43fa418619", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/git.ts": "2e68f1fa5ba534ee32db204bcc357f987437dffe5d87c1a0a9c47850fa654419", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/infisical.ts": "77789ea377250f0e762d01f1a8d378636bc520e7291aa9e82c5321c4059b6205", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/jq_ghrel.ts": "f685342c833c96637732fb28556f411f4537e85292046473f2b0d7f28c66ec8c", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/meta_cli_ghrel.ts": "0d5128cd3a15eece3fdf49c0697f5354e37ede6388e058dde572699634df1464", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/mod.ts": "eec07e249a69eca52620d9b770d0aa3266157dd87503e776da6da261683e6186", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/mold.ts": "b916227b48d7aa307ccc7d54c66724a41baa67be82bd558b5b9a35db4179c2f3", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/node.ts": "a98a095d3405a4907acfb620ff43babb0771d51ecaed87af8d1816c1cecd009b", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/npmi.ts": "056c7e733b1157601647630b9d460f19b5416dccc20415dd275d7ce972f09c39", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/opentofu_ghrel.ts": "46ef05d30772d36b91d88a2dc1aae31e096c59ba6ecf82af08359996c1476725", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/pipi.ts": "91354d0cfcacb4a3e54cd8436694fad56cb2c32de7d6710a0f5d0671bac1d1be", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/pnpm.ts": "41e7f473a687123ae96ab14a3a04f67ef0c4b44eea6747448826dbdae00bfdde", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/protoc.ts": "ef93af8f37d8186c2220b6d2f760b5da10decaa3e9fe7768003ee319d32335bf", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/ruff.ts": "3eb6963d2beb643ab8adc152e84811ea28e993c9d48b442e3255a49582529c3b", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/rust.ts": "264e755c4fedb9d6a68b94a3dd2e23d3bff2e6ab538a57babf5d94afa4800deb", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/rustup.ts": "0a8033d24fb6be84585db545b2592b868d36182907565fe23454e9a0262618cc", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/tar.ts": "c3c43a99f8a9b1c160286cbc6240f59658d994856eeacaee479f645ece44d6c4", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/temporal_cli.ts": "a5db59114e294b58715349e72e3d7e868274d4a25d7de027afa0470c5585ed9c", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/terraform.ts": "035fbdd3a6b858bd302c440fc0a588fb40ae57959685af837f8a4e34302b55a7", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/unzip.ts": "c4559c627246f9c051571bbdff8c63ab15780ffd9e71656a9055488cc3bf32c3", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/wasmedge.ts": "b74a35190b79be686d2c8615c291b883da21e0caea36a8a32340fba93694b8e0", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/ports/zstd.ts": "fb8334b7b43ef34ba60ad391460e2fabb62889f77eade7798c823b14842cea45", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/setup_logger.ts": "f8a206bda0595497d6f4718032d4a959000b32ef3346d4b507777eec6a169458", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/std.ts": "74515b1d816e643860b2a94409a49c08d8478d756c1fcae0dce95dde2c5c7162", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/std/copyLock.ts": "a47725f058cc8120914629bd0d4488345f168e80f1b3b286a64d4d1e919d6599", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/std/sedLock.ts": "115bcf40bb13435e579df24919d1a0f9be3d3ec96c442812c9ae4ceb335932aa", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/utils/logger.ts": "fcbafb35ae4b812412b9b301ce6d06b8b9798f94ebebe3f92677e25e4b19af3c", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/utils/mod.ts": "25bfdd222d6afec5b3f0a7e647e3d9b12abed6d222b49a4b2e95c6bbe266f533", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/utils/unarchive.ts": "f6d0e9e75f470eeef5aecd0089169f4350fc30ebfdc05466bb7b30042294d6d3", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/utils/url.ts": "e1ada6fd30fc796b8918c88456ea1b5bbd87a07d0a0538b092b91fd2bb9b7623", - "https://raw.githubusercontent.com/metatypedev/ghjk/44d9a41/utils/worker.ts": "ac4caf72a36d2e4af4f4e92f2e0a95f9fc2324b568640f24c7c2ff6dc0c11d62", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/deps/cli.ts": "aac025f9372ad413b9c2663dc7f61affd597820d9448f010a510d541df3b56ea", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/deps/common.ts": "f775710b66a9099b98651cd3831906466e9b83ef98f2e5c080fd59ee801c28d4", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/deps/ports.ts": "3c60d1f7ab626ffdd81b37f4e83a780910936480da8fe24f4ccceaefa207d339", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/files/deno/mod.ts": "1b8204c3df18b908408b2148b48af788e669d0debbeb8ba119418ab1ddf1ab8f", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/files/deno/worker.ts": "8ded400d70a0bd40e281ceb1ffcdc82578443caf9c481b9eee77166472784282", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/files/mod.ts": "a934ddb4803896e27b40644fe75623d584e01f30bbe1e16eb26bd772aa5b6064", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/host/mod.ts": "6d1ae2174e495945a7ebab6e868e512c166113691bd864769d9542b8547c3b17", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/host/types.ts": "f450d9b9c0eced2650262d02455aa6f794de0edd6b052aade256882148e5697f", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/install/mod.ts": "aa54eb3e119f28d33e61645c89669da292ee00376068ead8f45be2807e7a9989", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/install/utils.ts": "d4634d4fc0e963f540402b4ca7eb5dcba340eaa0d8fceb43af57d722ad267115", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/main.ts": "ecd5e83be2d8f351058ad44424cad1f36dd2e3d76f6e8409afc47682a9eff01a", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/mod.ts": "1d31b4f801ae2ebad052d219236699c4f227b6ce53c6e5016deaed5fcd00dbb6", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/modules/envs/inter.ts": "84805fa208754a08f185dca7a5236de3760bbc1d0df96af86ea5fd7778f827a2", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/modules/envs/mod.ts": "5f37b9f155808f8d6d51e1f16f58c07914d8c7d8070bc5c2fb5076ab748798a7", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/modules/envs/posix.ts": "09e410e3fea9c303a5148ff2a22697474320442b9fea0bd3fc932d6828fe820f", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/modules/envs/reducer.ts": "50517084caaf73ce6618141ee4d97795060a0d3169651da7abd7251a3204465a", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/modules/envs/types.ts": "ab9715cf02e9d73f553ae757db347863be23e1e9daf94d18aab716fc27b3dbc1", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/modules/mod.ts": "fc1cb9176c6557b44ae9c6536fa51c6c4f80ac01fc476d15b0a217e70cb0d176", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/modules/ports/ambient.ts": "823ec8d98702a60e6bfcdbeb64b69dc9f5039e73a1f10e87cd51210c1aaf52d5", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/modules/ports/base.ts": "8ef8a8de372420bddcd63a1b363937f43d898059e99478a58621e8432bcd5891", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/modules/ports/db.ts": "a309d1058f66079a481141c3f1733d928b9af8a37b7ce911b1228f70fd24df0f", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/modules/ports/ghrel.ts": "ebbc30a5c31244131d937eadca73fbc099c9e7bdf0ad4f668766d4388ede143c", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/modules/ports/inter.ts": "b3999e73d73d7f928a8de86e5e2261fe6b1450ceedfb54f24537bf0803532ed0", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/modules/ports/mod.ts": "78db7040e724f84c95b1a0fdeaf0cfc53382482e8905cd352189756b953556cc", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/modules/ports/reducers.ts": "d04e813652101f67f946242df68429ed5540e499fbdb7776b8be5703f16754c8", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/modules/ports/std.ts": "985902519aafef6e8e6aecc8922e70abdea5b8e97d5439bf94338b93242fe11f", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/modules/ports/sync.ts": "a7a297f6b098360d56af168692f3cff96f8ceeb5189e5baa249e094f8d9c42ef", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/modules/ports/types.ts": "f4dbd1a3f4b7f539b3a85418617d25adbf710b54144161880d48f6c4ec032eee", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/modules/ports/types/platform.ts": "0ecffeda71919293f9ffdb6c564ddea4f23bc85c4e640b08ea78225d34387fdc", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/modules/ports/utils.ts": "6b14b331cce66bd46e7aec51f02424327d819150f16d3f72a6b0aaf7aee43c09", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/modules/ports/worker.ts": "6b76ba1efb2e47a82582fc48bcc6264fe153a166beffccde1a9a3a185024c337", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/modules/std.ts": "419d6b04680f73f7b252257ab287d68c1571cee4347301c53278e2b53df21c4a", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/modules/tasks/deno.ts": "2b61092e13787df2e9b310702b78259d7af912d53b3957bc1c91a6669cdc53c0", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/modules/tasks/exec.ts": "dd92c2d73f7e8d7f942799ec8216ff0e1b334b7ef3997af55a18edee1b6fe42a", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/modules/tasks/inter.ts": "63e8f2860f7e3b4d95b6f61ca56aeb8567e4f265aa9c22cace6c8075edd6210f", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/modules/tasks/mod.ts": "334b18d7c110cc05483be96353e342425c0033b7410c271a8a47d2b18308c73e", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/modules/tasks/types.ts": "072a34bd0749428bad4d612cc86abe463d4d4f74dc56cf0a48a1f41650e2399b", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/modules/types.ts": "c0f212b686a2721d076e9aeb127596c7cbc939758e2cc32fd1d165a8fb320a87", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/port.ts": "c039a010dee7dfd978478cf4c5e2256c643135e10f33c30a09f8db9915e9d89d", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/act.ts": "2ce6b8fddf61db12ba69b7cad6985237a2962ca79853edbddee5bfb49c47d1ab", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/asdf.ts": "11d41bcad5981e014478343270f05bac265990e801c525e3288113d89bd287be", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/asdf_plugin_git.ts": "a3742fcd994ded231febf33754b087ab56393a799297b26315c2cf8a388a7f82", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/cargo-binstall.ts": "72860580e6f6db9ec7ba74dbe391ad98ed49b4ff43247661b27701f4e683d41b", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/cargobi.ts": "2bf25e20c19acf7badbaa01817263f9924dd3c31b0aaa934a8b6769e235f8a57", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/cmake.ts": "2359a20318694912fea281b144dbcbe542b3c2de72fc3da114e717d2afcd94e1", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/cpy_bs.ts": "4066e5eb094e72be4dec2428fb7f99231dcc5c4e2db7b5ea2373a9ad9ce28662", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/curl.ts": "58acd2a158187f448d940f45bfcd87c9b4884db127dcbaaaef27258bb4ebce92", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/deno_ghrel.ts": "eca02a93ceb62ad9fb7f395361d32da0d5657aba5f7856c8ae0109135da0e070", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/earthly.ts": "7a3c8cae1631f670105a63bc41c47a49da6fc777968c0e9546c55d43fa418619", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/git.ts": "2e68f1fa5ba534ee32db204bcc357f987437dffe5d87c1a0a9c47850fa654419", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/infisical.ts": "77789ea377250f0e762d01f1a8d378636bc520e7291aa9e82c5321c4059b6205", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/jq_ghrel.ts": "f685342c833c96637732fb28556f411f4537e85292046473f2b0d7f28c66ec8c", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/meta_cli_ghrel.ts": "0d5128cd3a15eece3fdf49c0697f5354e37ede6388e058dde572699634df1464", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/mod.ts": "a25ca4a4ca5e640ef436195cd0b5d0b1be33fa7f770e2d40a8eec6fb2b23838a", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/mold.ts": "b916227b48d7aa307ccc7d54c66724a41baa67be82bd558b5b9a35db4179c2f3", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/node.ts": "a98a095d3405a4907acfb620ff43babb0771d51ecaed87af8d1816c1cecd009b", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/npmi.ts": "056c7e733b1157601647630b9d460f19b5416dccc20415dd275d7ce972f09c39", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/opentofu_ghrel.ts": "46ef05d30772d36b91d88a2dc1aae31e096c59ba6ecf82af08359996c1476725", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/pipi.ts": "a4abf1bd197f01e0fbc68bfb60bdb43849d9719654e1033820d3fb4ce4f36449", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/pnpm.ts": "41e7f473a687123ae96ab14a3a04f67ef0c4b44eea6747448826dbdae00bfdde", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/protoc.ts": "ef93af8f37d8186c2220b6d2f760b5da10decaa3e9fe7768003ee319d32335bf", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/ruff.ts": "2a3246bf3d21482ea62c3e801e58ef760fa6b30d4652e5be55c9051ecf6df72e", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/rust.ts": "264e755c4fedb9d6a68b94a3dd2e23d3bff2e6ab538a57babf5d94afa4800deb", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/rustup.ts": "0a8033d24fb6be84585db545b2592b868d36182907565fe23454e9a0262618cc", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/tar.ts": "c3c43a99f8a9b1c160286cbc6240f59658d994856eeacaee479f645ece44d6c4", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/temporal_cli.ts": "a5db59114e294b58715349e72e3d7e868274d4a25d7de027afa0470c5585ed9c", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/terraform.ts": "035fbdd3a6b858bd302c440fc0a588fb40ae57959685af837f8a4e34302b55a7", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/unzip.ts": "c4559c627246f9c051571bbdff8c63ab15780ffd9e71656a9055488cc3bf32c3", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/wasmedge.ts": "b74a35190b79be686d2c8615c291b883da21e0caea36a8a32340fba93694b8e0", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/ports/zstd.ts": "fb8334b7b43ef34ba60ad391460e2fabb62889f77eade7798c823b14842cea45", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/setup_logger.ts": "f8a206bda0595497d6f4718032d4a959000b32ef3346d4b507777eec6a169458", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/std.ts": "74515b1d816e643860b2a94409a49c08d8478d756c1fcae0dce95dde2c5c7162", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/std/copyLock.ts": "a47725f058cc8120914629bd0d4488345f168e80f1b3b286a64d4d1e919d6599", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/std/sedLock.ts": "115bcf40bb13435e579df24919d1a0f9be3d3ec96c442812c9ae4ceb335932aa", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/utils/logger.ts": "fcbafb35ae4b812412b9b301ce6d06b8b9798f94ebebe3f92677e25e4b19af3c", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/utils/mod.ts": "527f13e6196167deaa6c100b39dcb76d9a541afbe68d005d77776b668d07961f", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/utils/unarchive.ts": "f6d0e9e75f470eeef5aecd0089169f4350fc30ebfdc05466bb7b30042294d6d3", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/utils/url.ts": "e1ada6fd30fc796b8918c88456ea1b5bbd87a07d0a0538b092b91fd2bb9b7623", - "https://raw.githubusercontent.com/metatypedev/ghjk/8d50518/utils/worker.ts": "ac4caf72a36d2e4af4f4e92f2e0a95f9fc2324b568640f24c7c2ff6dc0c11d62", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/deps/cli.ts": "aac025f9372ad413b9c2663dc7f61affd597820d9448f010a510d541df3b56ea", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/deps/common.ts": "f775710b66a9099b98651cd3831906466e9b83ef98f2e5c080fd59ee801c28d4", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/deps/ports.ts": "3c60d1f7ab626ffdd81b37f4e83a780910936480da8fe24f4ccceaefa207d339", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/files/deno/mod.ts": "1b8204c3df18b908408b2148b48af788e669d0debbeb8ba119418ab1ddf1ab8f", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/files/deno/worker.ts": "8ded400d70a0bd40e281ceb1ffcdc82578443caf9c481b9eee77166472784282", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/files/mod.ts": "a934ddb4803896e27b40644fe75623d584e01f30bbe1e16eb26bd772aa5b6064", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/host/mod.ts": "6d1ae2174e495945a7ebab6e868e512c166113691bd864769d9542b8547c3b17", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/host/types.ts": "f450d9b9c0eced2650262d02455aa6f794de0edd6b052aade256882148e5697f", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/install/mod.ts": "aa54eb3e119f28d33e61645c89669da292ee00376068ead8f45be2807e7a9989", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/install/utils.ts": "d4634d4fc0e963f540402b4ca7eb5dcba340eaa0d8fceb43af57d722ad267115", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/main.ts": "ecd5e83be2d8f351058ad44424cad1f36dd2e3d76f6e8409afc47682a9eff01a", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/mod.ts": "1d31b4f801ae2ebad052d219236699c4f227b6ce53c6e5016deaed5fcd00dbb6", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/envs/inter.ts": "84805fa208754a08f185dca7a5236de3760bbc1d0df96af86ea5fd7778f827a2", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/envs/mod.ts": "5f37b9f155808f8d6d51e1f16f58c07914d8c7d8070bc5c2fb5076ab748798a7", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/envs/posix.ts": "09e410e3fea9c303a5148ff2a22697474320442b9fea0bd3fc932d6828fe820f", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/envs/reducer.ts": "50517084caaf73ce6618141ee4d97795060a0d3169651da7abd7251a3204465a", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/envs/types.ts": "ab9715cf02e9d73f553ae757db347863be23e1e9daf94d18aab716fc27b3dbc1", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/mod.ts": "fc1cb9176c6557b44ae9c6536fa51c6c4f80ac01fc476d15b0a217e70cb0d176", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/ports/ambient.ts": "823ec8d98702a60e6bfcdbeb64b69dc9f5039e73a1f10e87cd51210c1aaf52d5", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/ports/base.ts": "8ef8a8de372420bddcd63a1b363937f43d898059e99478a58621e8432bcd5891", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/ports/db.ts": "a309d1058f66079a481141c3f1733d928b9af8a37b7ce911b1228f70fd24df0f", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/ports/ghrel.ts": "ebbc30a5c31244131d937eadca73fbc099c9e7bdf0ad4f668766d4388ede143c", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/ports/inter.ts": "b3999e73d73d7f928a8de86e5e2261fe6b1450ceedfb54f24537bf0803532ed0", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/ports/mod.ts": "78db7040e724f84c95b1a0fdeaf0cfc53382482e8905cd352189756b953556cc", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/ports/reducers.ts": "d04e813652101f67f946242df68429ed5540e499fbdb7776b8be5703f16754c8", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/ports/std.ts": "985902519aafef6e8e6aecc8922e70abdea5b8e97d5439bf94338b93242fe11f", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/ports/sync.ts": "a7a297f6b098360d56af168692f3cff96f8ceeb5189e5baa249e094f8d9c42ef", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/ports/types.ts": "f4dbd1a3f4b7f539b3a85418617d25adbf710b54144161880d48f6c4ec032eee", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/ports/types/platform.ts": "0ecffeda71919293f9ffdb6c564ddea4f23bc85c4e640b08ea78225d34387fdc", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/ports/utils.ts": "6b14b331cce66bd46e7aec51f02424327d819150f16d3f72a6b0aaf7aee43c09", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/ports/worker.ts": "6b76ba1efb2e47a82582fc48bcc6264fe153a166beffccde1a9a3a185024c337", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/std.ts": "419d6b04680f73f7b252257ab287d68c1571cee4347301c53278e2b53df21c4a", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/tasks/deno.ts": "2b61092e13787df2e9b310702b78259d7af912d53b3957bc1c91a6669cdc53c0", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/tasks/exec.ts": "dd92c2d73f7e8d7f942799ec8216ff0e1b334b7ef3997af55a18edee1b6fe42a", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/tasks/inter.ts": "63e8f2860f7e3b4d95b6f61ca56aeb8567e4f265aa9c22cace6c8075edd6210f", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/tasks/mod.ts": "334b18d7c110cc05483be96353e342425c0033b7410c271a8a47d2b18308c73e", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/tasks/types.ts": "072a34bd0749428bad4d612cc86abe463d4d4f74dc56cf0a48a1f41650e2399b", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/types.ts": "c0f212b686a2721d076e9aeb127596c7cbc939758e2cc32fd1d165a8fb320a87", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/port.ts": "c039a010dee7dfd978478cf4c5e2256c643135e10f33c30a09f8db9915e9d89d", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/act.ts": "2ce6b8fddf61db12ba69b7cad6985237a2962ca79853edbddee5bfb49c47d1ab", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/asdf.ts": "11d41bcad5981e014478343270f05bac265990e801c525e3288113d89bd287be", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/asdf_plugin_git.ts": "a3742fcd994ded231febf33754b087ab56393a799297b26315c2cf8a388a7f82", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/cargo-binstall.ts": "72860580e6f6db9ec7ba74dbe391ad98ed49b4ff43247661b27701f4e683d41b", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/cargobi.ts": "51c95fe47132ee35df2cd34c67d10d2e53dc10edd438c0f4f70eb644e81f2563", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/cmake.ts": "745bcfdbffdd5d7cb0314e4e618b764a3a0f7d19246ec8b9134b1ff981bc2091", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/cpy_bs.ts": "4066e5eb094e72be4dec2428fb7f99231dcc5c4e2db7b5ea2373a9ad9ce28662", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/curl.ts": "58acd2a158187f448d940f45bfcd87c9b4884db127dcbaaaef27258bb4ebce92", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/deno_ghrel.ts": "eca02a93ceb62ad9fb7f395361d32da0d5657aba5f7856c8ae0109135da0e070", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/earthly.ts": "7a3c8cae1631f670105a63bc41c47a49da6fc777968c0e9546c55d43fa418619", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/git.ts": "2e68f1fa5ba534ee32db204bcc357f987437dffe5d87c1a0a9c47850fa654419", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/infisical.ts": "77789ea377250f0e762d01f1a8d378636bc520e7291aa9e82c5321c4059b6205", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/jq_ghrel.ts": "f685342c833c96637732fb28556f411f4537e85292046473f2b0d7f28c66ec8c", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/meta_cli_ghrel.ts": "0d5128cd3a15eece3fdf49c0697f5354e37ede6388e058dde572699634df1464", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/mod.ts": "a25ca4a4ca5e640ef436195cd0b5d0b1be33fa7f770e2d40a8eec6fb2b23838a", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/mold.ts": "b916227b48d7aa307ccc7d54c66724a41baa67be82bd558b5b9a35db4179c2f3", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/node.ts": "a98a095d3405a4907acfb620ff43babb0771d51ecaed87af8d1816c1cecd009b", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/npmi.ts": "056c7e733b1157601647630b9d460f19b5416dccc20415dd275d7ce972f09c39", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/opentofu_ghrel.ts": "46ef05d30772d36b91d88a2dc1aae31e096c59ba6ecf82af08359996c1476725", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/pipi.ts": "a4abf1bd197f01e0fbc68bfb60bdb43849d9719654e1033820d3fb4ce4f36449", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/pnpm.ts": "41e7f473a687123ae96ab14a3a04f67ef0c4b44eea6747448826dbdae00bfdde", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/protoc.ts": "ef93af8f37d8186c2220b6d2f760b5da10decaa3e9fe7768003ee319d32335bf", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/ruff.ts": "2a3246bf3d21482ea62c3e801e58ef760fa6b30d4652e5be55c9051ecf6df72e", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/rust.ts": "e8b58f3ccb5411f0bba4bf3aadc040017da11eab4f6820cee03ab8f764383ca2", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/rustup.ts": "0a8033d24fb6be84585db545b2592b868d36182907565fe23454e9a0262618cc", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/tar.ts": "c3c43a99f8a9b1c160286cbc6240f59658d994856eeacaee479f645ece44d6c4", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/temporal_cli.ts": "a5db59114e294b58715349e72e3d7e868274d4a25d7de027afa0470c5585ed9c", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/terraform.ts": "035fbdd3a6b858bd302c440fc0a588fb40ae57959685af837f8a4e34302b55a7", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/unzip.ts": "c4559c627246f9c051571bbdff8c63ab15780ffd9e71656a9055488cc3bf32c3", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/wasmedge.ts": "b74a35190b79be686d2c8615c291b883da21e0caea36a8a32340fba93694b8e0", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/zstd.ts": "fb8334b7b43ef34ba60ad391460e2fabb62889f77eade7798c823b14842cea45", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/setup_logger.ts": "f8a206bda0595497d6f4718032d4a959000b32ef3346d4b507777eec6a169458", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/std.ts": "74515b1d816e643860b2a94409a49c08d8478d756c1fcae0dce95dde2c5c7162", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/std/copyLock.ts": "a47725f058cc8120914629bd0d4488345f168e80f1b3b286a64d4d1e919d6599", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/std/sedLock.ts": "115bcf40bb13435e579df24919d1a0f9be3d3ec96c442812c9ae4ceb335932aa", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/utils/logger.ts": "fcbafb35ae4b812412b9b301ce6d06b8b9798f94ebebe3f92677e25e4b19af3c", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/utils/mod.ts": "647acf23e785d1a44b1d1da53d238fa9d921ccf841b5e65d01c8be0589118395", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/utils/unarchive.ts": "f6d0e9e75f470eeef5aecd0089169f4350fc30ebfdc05466bb7b30042294d6d3", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/utils/url.ts": "e1ada6fd30fc796b8918c88456ea1b5bbd87a07d0a0538b092b91fd2bb9b7623", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/utils/worker.ts": "ac4caf72a36d2e4af4f4e92f2e0a95f9fc2324b568640f24c7c2ff6dc0c11d62" + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/deps/cli.ts": "aac025f9372ad413b9c2663dc7f61affd597820d9448f010a510d541df3b56ea", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/deps/common.ts": "f775710b66a9099b98651cd3831906466e9b83ef98f2e5c080fd59ee801c28d4", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/deps/ports.ts": "3c60d1f7ab626ffdd81b37f4e83a780910936480da8fe24f4ccceaefa207d339", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/files/deno/mod.ts": "1b8204c3df18b908408b2148b48af788e669d0debbeb8ba119418ab1ddf1ab8f", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/files/deno/worker.ts": "8ded400d70a0bd40e281ceb1ffcdc82578443caf9c481b9eee77166472784282", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/files/mod.ts": "44a8874c6ee9f086b7a521d4956c1802be201d01f9e91329d52a4b96738f7a34", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/host/mod.ts": "af5a9704c3a5b410b322afe0bc8caaaac5b28e1e1591d82b0c5fb53f92cbc97f", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/host/types.ts": "f450d9b9c0eced2650262d02455aa6f794de0edd6b052aade256882148e5697f", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/install/mod.ts": "aa54eb3e119f28d33e61645c89669da292ee00376068ead8f45be2807e7a9989", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/install/utils.ts": "d4634d4fc0e963f540402b4ca7eb5dcba340eaa0d8fceb43af57d722ad267115", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/main.ts": "ecd5e83be2d8f351058ad44424cad1f36dd2e3d76f6e8409afc47682a9eff01a", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/mod.ts": "1d31b4f801ae2ebad052d219236699c4f227b6ce53c6e5016deaed5fcd00dbb6", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/envs/inter.ts": "84805fa208754a08f185dca7a5236de3760bbc1d0df96af86ea5fd7778f827a2", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/envs/mod.ts": "5f37b9f155808f8d6d51e1f16f58c07914d8c7d8070bc5c2fb5076ab748798a7", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/envs/posix.ts": "b22f9564d9773548d537c95265e694a2630c3fe1fd63354d6f4790e275545299", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/envs/reducer.ts": "76ee6974c9d4885da0898e01c498dcfdd99a3652a5a564d679577931a680e781", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/envs/types.ts": "9ff28d47aa60042df42fbb98a46f7689d8111be462237f5fb81771011e429088", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/mod.ts": "fc1cb9176c6557b44ae9c6536fa51c6c4f80ac01fc476d15b0a217e70cb0d176", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/ambient.ts": "823ec8d98702a60e6bfcdbeb64b69dc9f5039e73a1f10e87cd51210c1aaf52d5", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/base.ts": "8ef8a8de372420bddcd63a1b363937f43d898059e99478a58621e8432bcd5891", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/db.ts": "a309d1058f66079a481141c3f1733d928b9af8a37b7ce911b1228f70fd24df0f", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/ghrel.ts": "ebbc30a5c31244131d937eadca73fbc099c9e7bdf0ad4f668766d4388ede143c", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/inter.ts": "b3999e73d73d7f928a8de86e5e2261fe6b1450ceedfb54f24537bf0803532ed0", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/mod.ts": "646cfe12c181f378ffd865890e07ba0a2c92b70cf10687f43de49864ca15c482", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/reducers.ts": "d04e813652101f67f946242df68429ed5540e499fbdb7776b8be5703f16754c8", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/std.ts": "985902519aafef6e8e6aecc8922e70abdea5b8e97d5439bf94338b93242fe11f", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/sync.ts": "a7a297f6b098360d56af168692f3cff96f8ceeb5189e5baa249e094f8d9c42ef", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/types.ts": "f4dbd1a3f4b7f539b3a85418617d25adbf710b54144161880d48f6c4ec032eee", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/types/platform.ts": "0ecffeda71919293f9ffdb6c564ddea4f23bc85c4e640b08ea78225d34387fdc", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/utils.ts": "6b14b331cce66bd46e7aec51f02424327d819150f16d3f72a6b0aaf7aee43c09", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/worker.ts": "6b76ba1efb2e47a82582fc48bcc6264fe153a166beffccde1a9a3a185024c337", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/std.ts": "419d6b04680f73f7b252257ab287d68c1571cee4347301c53278e2b53df21c4a", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/tasks/deno.ts": "75b85d8cdc129e56d7bd1bfbfdc4a6f4685e86933c41908e48fbc51be7a57fee", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/tasks/exec.ts": "ddc6bc7cbed464fdd94038a0df8668138411e94e49ae639615b93e734e37d311", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/tasks/inter.ts": "63e8f2860f7e3b4d95b6f61ca56aeb8567e4f265aa9c22cace6c8075edd6210f", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/tasks/mod.ts": "334b18d7c110cc05483be96353e342425c0033b7410c271a8a47d2b18308c73e", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/tasks/types.ts": "072a34bd0749428bad4d612cc86abe463d4d4f74dc56cf0a48a1f41650e2399b", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/types.ts": "c0f212b686a2721d076e9aeb127596c7cbc939758e2cc32fd1d165a8fb320a87", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/port.ts": "c039a010dee7dfd978478cf4c5e2256c643135e10f33c30a09f8db9915e9d89d", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/act.ts": "2ce6b8fddf61db12ba69b7cad6985237a2962ca79853edbddee5bfb49c47d1ab", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/asdf.ts": "11d41bcad5981e014478343270f05bac265990e801c525e3288113d89bd287be", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/asdf_plugin_git.ts": "a3742fcd994ded231febf33754b087ab56393a799297b26315c2cf8a388a7f82", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/cargo-binstall.ts": "72860580e6f6db9ec7ba74dbe391ad98ed49b4ff43247661b27701f4e683d41b", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/cargobi.ts": "51c95fe47132ee35df2cd34c67d10d2e53dc10edd438c0f4f70eb644e81f2563", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/cmake.ts": "745bcfdbffdd5d7cb0314e4e618b764a3a0f7d19246ec8b9134b1ff981bc2091", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/cpy_bs.ts": "4066e5eb094e72be4dec2428fb7f99231dcc5c4e2db7b5ea2373a9ad9ce28662", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/curl.ts": "58acd2a158187f448d940f45bfcd87c9b4884db127dcbaaaef27258bb4ebce92", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/deno_ghrel.ts": "eca02a93ceb62ad9fb7f395361d32da0d5657aba5f7856c8ae0109135da0e070", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/earthly.ts": "7a3c8cae1631f670105a63bc41c47a49da6fc777968c0e9546c55d43fa418619", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/git.ts": "2e68f1fa5ba534ee32db204bcc357f987437dffe5d87c1a0a9c47850fa654419", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/infisical.ts": "77789ea377250f0e762d01f1a8d378636bc520e7291aa9e82c5321c4059b6205", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/jq_ghrel.ts": "f685342c833c96637732fb28556f411f4537e85292046473f2b0d7f28c66ec8c", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/meta_cli_ghrel.ts": "0d5128cd3a15eece3fdf49c0697f5354e37ede6388e058dde572699634df1464", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/mod.ts": "a25ca4a4ca5e640ef436195cd0b5d0b1be33fa7f770e2d40a8eec6fb2b23838a", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/mold.ts": "b916227b48d7aa307ccc7d54c66724a41baa67be82bd558b5b9a35db4179c2f3", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/node.ts": "a98a095d3405a4907acfb620ff43babb0771d51ecaed87af8d1816c1cecd009b", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/npmi.ts": "056c7e733b1157601647630b9d460f19b5416dccc20415dd275d7ce972f09c39", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/opentofu_ghrel.ts": "46ef05d30772d36b91d88a2dc1aae31e096c59ba6ecf82af08359996c1476725", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/pipi.ts": "a4abf1bd197f01e0fbc68bfb60bdb43849d9719654e1033820d3fb4ce4f36449", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/pnpm.ts": "41e7f473a687123ae96ab14a3a04f67ef0c4b44eea6747448826dbdae00bfdde", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/protoc.ts": "ef93af8f37d8186c2220b6d2f760b5da10decaa3e9fe7768003ee319d32335bf", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/ruff.ts": "2a3246bf3d21482ea62c3e801e58ef760fa6b30d4652e5be55c9051ecf6df72e", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/rust.ts": "e8b58f3ccb5411f0bba4bf3aadc040017da11eab4f6820cee03ab8f764383ca2", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/rustup.ts": "0a8033d24fb6be84585db545b2592b868d36182907565fe23454e9a0262618cc", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/tar.ts": "c3c43a99f8a9b1c160286cbc6240f59658d994856eeacaee479f645ece44d6c4", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/temporal_cli.ts": "a5db59114e294b58715349e72e3d7e868274d4a25d7de027afa0470c5585ed9c", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/terraform.ts": "035fbdd3a6b858bd302c440fc0a588fb40ae57959685af837f8a4e34302b55a7", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/unzip.ts": "c4559c627246f9c051571bbdff8c63ab15780ffd9e71656a9055488cc3bf32c3", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/wasmedge.ts": "b74a35190b79be686d2c8615c291b883da21e0caea36a8a32340fba93694b8e0", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/zstd.ts": "fb8334b7b43ef34ba60ad391460e2fabb62889f77eade7798c823b14842cea45", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/setup_logger.ts": "f8a206bda0595497d6f4718032d4a959000b32ef3346d4b507777eec6a169458", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/std.ts": "74515b1d816e643860b2a94409a49c08d8478d756c1fcae0dce95dde2c5c7162", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/std/copyLock.ts": "a47725f058cc8120914629bd0d4488345f168e80f1b3b286a64d4d1e919d6599", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/std/sedLock.ts": "115bcf40bb13435e579df24919d1a0f9be3d3ec96c442812c9ae4ceb335932aa", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/utils/logger.ts": "fcbafb35ae4b812412b9b301ce6d06b8b9798f94ebebe3f92677e25e4b19af3c", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/utils/mod.ts": "25901b5a03625353cc0d9c024daca806eb2513b153faede5ecad73b428542721", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/utils/unarchive.ts": "f6d0e9e75f470eeef5aecd0089169f4350fc30ebfdc05466bb7b30042294d6d3", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/utils/url.ts": "e1ada6fd30fc796b8918c88456ea1b5bbd87a07d0a0538b092b91fd2bb9b7623", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/utils/worker.ts": "ac4caf72a36d2e4af4f4e92f2e0a95f9fc2324b568640f24c7c2ff6dc0c11d62" } } diff --git a/.ghjk/lock.json b/.ghjk/lock.json index ccddfef7ce..ef1652091a 100644 --- a/.ghjk/lock.json +++ b/.ghjk/lock.json @@ -18,22 +18,22 @@ "specifiedVersion": true }, "bciqmpujkkyxmzdz7sxpvi2pmajzfmg6gofplyqn43av2styxu2ng7sy": { - "version": "7.81.0", + "version": "8.9.0", "buildDepConfigs": {}, "portRef": "curl_aa@0.1.0", "specifiedVersion": false }, "bciqngfjdds2gmnwhriiif3fjgqw2hhpm3ssqxlnq4kiwyk53uesmzgy": { - "version": "3.30.1", + "version": "3.30.2", "buildDepConfigs": { "curl_aa": { - "version": "7.81.0", + "version": "8.9.0", "buildDepConfigs": {}, "portRef": "curl_aa@0.1.0", "specifiedVersion": false }, "git_aa": { - "version": "2.34.1", + "version": "2.46.0", "buildDepConfigs": {}, "portRef": "git_aa@0.1.0", "specifiedVersion": false @@ -42,7 +42,7 @@ "version": "d631481e96", "buildDepConfigs": { "git_aa": { - "version": "2.34.1", + "version": "2.46.0", "buildDepConfigs": {}, "portRef": "git_aa@0.1.0", "specifiedVersion": false @@ -65,7 +65,7 @@ "specifiedVersion": false }, "bciqay4m4kmzfduj5t2clgejxgpe5zwper6lyyaxt7rhbjalaqd32nhq": { - "version": "2.34.1", + "version": "2.46.0", "buildDepConfigs": {}, "portRef": "git_aa@0.1.0", "specifiedVersion": false @@ -74,7 +74,7 @@ "version": "d631481e96", "buildDepConfigs": { "git_aa": { - "version": "2.34.1", + "version": "2.46.0", "buildDepConfigs": {}, "portRef": "git_aa@0.1.0", "specifiedVersion": false @@ -84,11 +84,17 @@ "pluginRepo": "https://github.com/asdf-community/asdf-cmake", "specifiedVersion": false }, + "bciqdryp66ydidszpw3nbjwf7d5lnyvw7xujujby4bnx5mdgdjabxcvi": { + "version": "2.46.0", + "buildDepConfigs": {}, + "portRef": "git_aa@0.1.0", + "specifiedVersion": true + }, "bciqkv7foyoio4wpti4yf2qrw5nphkgk2din6ba7mjv2w7hmgrv725ja": { "version": "v2.4.0", "buildDepConfigs": { "tar_aa": { - "version": "1.34", + "version": "1.35", "buildDepConfigs": {}, "portRef": "tar_aa@0.1.0", "specifiedVersion": false @@ -99,22 +105,22 @@ "specifiedVersion": true }, "bciqj4p5hoqweghbuvz52rupja7sqze34z63dd62nz632c5zxikv6ezy": { - "version": "1.34", + "version": "1.35", "buildDepConfigs": {}, "portRef": "tar_aa@0.1.0", "specifiedVersion": false }, "bciqjlw6cxddajjmznoemlmnu7mgbbm7a3hfmnd2x5oivwajmiqui5ey": { - "version": "v0.2.64", + "version": "v0.2.65", "buildDepConfigs": {}, "portRef": "act_ghrel@0.1.0", "specifiedVersion": false }, - "bciqprohqqm7hfj3qoio2y4slu2i4vgypv6c32g3djvj4pjduo5ns4mi": { + "bciqkdikxlnvysolt3rirxozdyaovts5jo7e6zdhto7pjgxss4rkjeui": { "version": "0.5.0", "buildDepConfigs": { "cargo_binstall_ghrel": { - "version": "v1.7.4", + "version": "v1.10.2", "buildDepConfigs": {}, "portRef": "cargo_binstall_ghrel@0.1.0", "specifiedVersion": false @@ -126,7 +132,7 @@ "version": "1.27.1", "buildDepConfigs": { "git_aa": { - "version": "2.34.1", + "version": "2.46.0", "buildDepConfigs": {}, "portRef": "git_aa@0.1.0", "specifiedVersion": false @@ -138,7 +144,7 @@ }, "portRef": "rust_rustup@0.1.0", "profile": "minimal", - "specifiedVersion": false + "specifiedVersion": true } }, "portRef": "cargobi_cratesio@0.1.0", @@ -147,19 +153,19 @@ "specifiedVersion": false }, "bciqeal5okt5zj763vhgsmf3afr5thrkqaitv6pb3wwegcwyb74gdyjq": { - "version": "v1.7.4", + "version": "v1.10.2", "buildDepConfigs": {}, "portRef": "cargo_binstall_ghrel@0.1.0", "specifiedVersion": false }, - "bciqeub366wrduva4y3rxegdjkhlebwsud56e5orgcpbkdrbhuxmvuxa": { + "bciqdm4ezstna7wryjatl75thl72622466u4ht2y25dkhvgu76kcnemi": { "version": "1.79.0", "buildDepConfigs": { "rustup_rustlang": { "version": "1.27.1", "buildDepConfigs": { "git_aa": { - "version": "2.34.1", + "version": "2.46.0", "buildDepConfigs": {}, "portRef": "git_aa@0.1.0", "specifiedVersion": false @@ -171,13 +177,13 @@ }, "portRef": "rust_rustup@0.1.0", "profile": "minimal", - "specifiedVersion": false + "specifiedVersion": true }, "bciqewpyjyfnnk4rbd6bbu5who2w6ve7dyt3inal72zg23cs4qnln32q": { "version": "1.27.1", "buildDepConfigs": { "git_aa": { - "version": "2.34.1", + "version": "2.46.0", "buildDepConfigs": {}, "portRef": "git_aa@0.1.0", "specifiedVersion": false @@ -186,11 +192,11 @@ "portRef": "rustup_rustlang@0.1.0", "specifiedVersion": false }, - "bciqk4pjtubgng2jbkpkky2mvqejegqpaqhqfreryyyy4ataduwhcfsq": { + "bciqg6addm4t4fpqgkcpukqvbdrkscd56popl3at6aljs4tfnsfl3iki": { "version": "0.3.4", "buildDepConfigs": { "cargo_binstall_ghrel": { - "version": "v1.7.4", + "version": "v1.10.2", "buildDepConfigs": {}, "portRef": "cargo_binstall_ghrel@0.1.0", "specifiedVersion": false @@ -202,7 +208,7 @@ "version": "1.27.1", "buildDepConfigs": { "git_aa": { - "version": "2.34.1", + "version": "2.46.0", "buildDepConfigs": {}, "portRef": "git_aa@0.1.0", "specifiedVersion": false @@ -214,7 +220,7 @@ }, "portRef": "rust_rustup@0.1.0", "profile": "minimal", - "specifiedVersion": false + "specifiedVersion": true } }, "portRef": "cargobi_cratesio@0.1.0", @@ -222,11 +228,11 @@ "locked": true, "specifiedVersion": false }, - "bciqny2uv3hqjpsra5eribww5ojitk3ndtkf2g44otv4xoi5waxfrwzq": { + "bciqjt35de5uwbp3qv3idkj7or2pu2gcun5jjkdttfupyuseslmjny5i": { "version": "2.4.0", "buildDepConfigs": { "cargo_binstall_ghrel": { - "version": "v1.7.4", + "version": "v1.10.2", "buildDepConfigs": {}, "portRef": "cargo_binstall_ghrel@0.1.0", "specifiedVersion": false @@ -238,7 +244,7 @@ "version": "1.27.1", "buildDepConfigs": { "git_aa": { - "version": "2.34.1", + "version": "2.46.0", "buildDepConfigs": {}, "portRef": "git_aa@0.1.0", "specifiedVersion": false @@ -250,7 +256,7 @@ }, "portRef": "rust_rustup@0.1.0", "profile": "minimal", - "specifiedVersion": false + "specifiedVersion": true } }, "portRef": "cargobi_cratesio@0.1.0", @@ -265,13 +271,13 @@ "version": "3.12.2", "buildDepConfigs": { "tar_aa": { - "version": "1.34", + "version": "1.35", "buildDepConfigs": {}, "portRef": "tar_aa@0.1.0", "specifiedVersion": false }, "zstd_aa": { - "version": "v1.4.8,", + "version": "v1.5.6,", "buildDepConfigs": {}, "portRef": "zstd_aa@0.1.0", "specifiedVersion": false @@ -290,13 +296,13 @@ "version": "3.12.2", "buildDepConfigs": { "tar_aa": { - "version": "1.34", + "version": "1.35", "buildDepConfigs": {}, "portRef": "tar_aa@0.1.0", "specifiedVersion": false }, "zstd_aa": { - "version": "v1.4.8,", + "version": "v1.5.6,", "buildDepConfigs": {}, "portRef": "zstd_aa@0.1.0", "specifiedVersion": false @@ -307,7 +313,7 @@ "specifiedVersion": true }, "bciqe6fwheayositrdk7rkr2ngdr4wizldakex23tgivss7w6z7g3q3y": { - "version": "v1.4.8,", + "version": "v1.5.6,", "buildDepConfigs": {}, "portRef": "zstd_aa@0.1.0", "specifiedVersion": false @@ -318,11 +324,11 @@ "portRef": "temporal_cli_ghrel@0.1.0", "specifiedVersion": true }, - "bciqkr4tyf7wvelz3rcifm47okxidbe76rb7r6nikj3aulqoxtgothlq": { + "bciqnvknjnd6ivod4sz4usxxbxmhhyp4kiahvyy4yn6erblbzfu2s7na": { "version": "1.33.0", "buildDepConfigs": { "cargo_binstall_ghrel": { - "version": "v1.7.4", + "version": "v1.10.2", "buildDepConfigs": {}, "portRef": "cargo_binstall_ghrel@0.1.0", "specifiedVersion": false @@ -334,7 +340,7 @@ "version": "1.27.1", "buildDepConfigs": { "git_aa": { - "version": "2.34.1", + "version": "2.46.0", "buildDepConfigs": {}, "portRef": "git_aa@0.1.0", "specifiedVersion": false @@ -346,7 +352,7 @@ }, "portRef": "rust_rustup@0.1.0", "profile": "minimal", - "specifiedVersion": false + "specifiedVersion": true } }, "portRef": "cargobi_cratesio@0.1.0", @@ -354,11 +360,11 @@ "locked": true, "specifiedVersion": true }, - "bciqijtlsvsgtcsuchl5tk6hxi4kcyuvdfq3lvm6lzn5o4xtiz6gzeiy": { + "bciqgustga36b3bmjpi76xcde43z3efxo2czwmvotcxlktxkhcizywiq": { "version": "0.2.5", "buildDepConfigs": { "cargo_binstall_ghrel": { - "version": "v1.7.4", + "version": "v1.10.2", "buildDepConfigs": {}, "portRef": "cargo_binstall_ghrel@0.1.0", "specifiedVersion": false @@ -370,7 +376,7 @@ "version": "1.27.1", "buildDepConfigs": { "git_aa": { - "version": "2.34.1", + "version": "2.46.0", "buildDepConfigs": {}, "portRef": "git_aa@0.1.0", "specifiedVersion": false @@ -382,7 +388,7 @@ }, "portRef": "rust_rustup@0.1.0", "profile": "minimal", - "specifiedVersion": false + "specifiedVersion": true } }, "portRef": "cargobi_cratesio@0.1.0", @@ -394,13 +400,13 @@ "version": "3.8.18", "buildDepConfigs": { "tar_aa": { - "version": "1.34", + "version": "1.35", "buildDepConfigs": {}, "portRef": "tar_aa@0.1.0", "specifiedVersion": false }, "zstd_aa": { - "version": "v1.4.8,", + "version": "v1.5.6,", "buildDepConfigs": {}, "portRef": "zstd_aa@0.1.0", "specifiedVersion": false @@ -417,13 +423,13 @@ "version": "3.12.2", "buildDepConfigs": { "tar_aa": { - "version": "1.34", + "version": "1.35", "buildDepConfigs": {}, "portRef": "tar_aa@0.1.0", "specifiedVersion": false }, "zstd_aa": { - "version": "v1.4.8,", + "version": "v1.5.6,", "buildDepConfigs": {}, "portRef": "zstd_aa@0.1.0", "specifiedVersion": false @@ -445,13 +451,13 @@ "version": "3.12.2", "buildDepConfigs": { "tar_aa": { - "version": "1.34", + "version": "1.35", "buildDepConfigs": {}, "portRef": "tar_aa@0.1.0", "specifiedVersion": false }, "zstd_aa": { - "version": "v1.4.8,", + "version": "v1.5.6,", "buildDepConfigs": {}, "portRef": "zstd_aa@0.1.0", "specifiedVersion": false @@ -470,7 +476,7 @@ "version": "v20.8.0", "buildDepConfigs": { "tar_aa": { - "version": "1.34", + "version": "1.35", "buildDepConfigs": {}, "portRef": "tar_aa@0.1.0", "specifiedVersion": false @@ -492,7 +498,7 @@ "version": "v20.8.0", "buildDepConfigs": { "tar_aa": { - "version": "1.34", + "version": "1.35", "buildDepConfigs": {}, "portRef": "tar_aa@0.1.0", "specifiedVersion": false @@ -506,11 +512,11 @@ "packageName": "node-gyp", "specifiedVersion": true }, - "bciqfmcxigz46qj767rnf5xnmhnmomqs55owt2unvwujke37suy6onsy": { + "bciqow7vi5yxnhumjr3am67ct5pnavar2e55wp2vlddesl43oyppw5kq": { "version": "0.116.1", "buildDepConfigs": { "cargo_binstall_ghrel": { - "version": "v1.7.4", + "version": "v1.10.2", "buildDepConfigs": {}, "portRef": "cargo_binstall_ghrel@0.1.0", "specifiedVersion": false @@ -522,7 +528,7 @@ "version": "1.27.1", "buildDepConfigs": { "git_aa": { - "version": "2.34.1", + "version": "2.46.0", "buildDepConfigs": {}, "portRef": "git_aa@0.1.0", "specifiedVersion": false @@ -534,7 +540,7 @@ }, "portRef": "rust_rustup@0.1.0", "profile": "minimal", - "specifiedVersion": false + "specifiedVersion": true } }, "portRef": "cargobi_cratesio@0.1.0", @@ -542,11 +548,11 @@ "locked": true, "specifiedVersion": true }, - "bciqbjgwmu5dkd4yxzfadkplzelrsikhguzi6taa57hishlvjfgbzl6y": { + "bciqgrinxolr2jfyj4smh3w5vwz6n4aacistocxbat5pz2fwf23glslq": { "version": "1.208.1", "buildDepConfigs": { "cargo_binstall_ghrel": { - "version": "v1.7.4", + "version": "v1.10.2", "buildDepConfigs": {}, "portRef": "cargo_binstall_ghrel@0.1.0", "specifiedVersion": false @@ -558,7 +564,7 @@ "version": "1.27.1", "buildDepConfigs": { "git_aa": { - "version": "2.34.1", + "version": "2.46.0", "buildDepConfigs": {}, "portRef": "git_aa@0.1.0", "specifiedVersion": false @@ -570,7 +576,7 @@ }, "portRef": "rust_rustup@0.1.0", "profile": "minimal", - "specifiedVersion": false + "specifiedVersion": true } }, "portRef": "cargobi_cratesio@0.1.0", @@ -585,13 +591,13 @@ "version": "3.12.2", "buildDepConfigs": { "tar_aa": { - "version": "1.34", + "version": "1.35", "buildDepConfigs": {}, "portRef": "tar_aa@0.1.0", "specifiedVersion": false }, "zstd_aa": { - "version": "v1.4.8,", + "version": "v1.5.6,", "buildDepConfigs": {}, "portRef": "zstd_aa@0.1.0", "specifiedVersion": false @@ -613,7 +619,7 @@ "version": "v20.8.0", "buildDepConfigs": { "tar_aa": { - "version": "1.34", + "version": "1.35", "buildDepConfigs": {}, "portRef": "tar_aa@0.1.0", "specifiedVersion": false @@ -627,335 +633,6 @@ "packageName": "@bytecodealliance/jco", "specifiedVersion": true }, - "bciqeju54u3m3aipax7w7snnfxwpqzx2h5c77jdl7hvgiendnydqivwa": { - "version": "nightly-2024-05-26", - "buildDepConfigs": { - "rustup_rustlang": { - "version": "1.27.1", - "buildDepConfigs": { - "git_aa": { - "version": "2.34.1", - "buildDepConfigs": {}, - "portRef": "git_aa@0.1.0", - "specifiedVersion": false - } - }, - "portRef": "rustup_rustlang@0.1.0", - "specifiedVersion": false - } - }, - "portRef": "rust_rustup@0.1.0", - "specifiedVersion": true - }, - "bciqk7gvbbprzpfqzvok7kucwsorxrvloqf2v2j6oy2rrsg4v7ucnmaq": { - "version": "0.1.47", - "buildDepConfigs": { - "cargo_binstall_ghrel": { - "version": "v1.7.4", - "buildDepConfigs": {}, - "portRef": "cargo_binstall_ghrel@0.1.0", - "specifiedVersion": false - }, - "rust_rustup": { - "version": "1.79.0", - "buildDepConfigs": { - "rustup_rustlang": { - "version": "1.27.1", - "buildDepConfigs": { - "git_aa": { - "version": "2.34.1", - "buildDepConfigs": {}, - "portRef": "git_aa@0.1.0", - "specifiedVersion": false - } - }, - "portRef": "rustup_rustlang@0.1.0", - "specifiedVersion": false - } - }, - "portRef": "rust_rustup@0.1.0", - "profile": "minimal", - "specifiedVersion": false - } - }, - "portRef": "cargobi_cratesio@0.1.0", - "crateName": "cargo-udeps", - "locked": true, - "specifiedVersion": true - }, - "bciqkdikxlnvysolt3rirxozdyaovts5jo7e6zdhto7pjgxss4rkjeui": { - "version": "0.5.0", - "buildDepConfigs": { - "cargo_binstall_ghrel": { - "version": "v1.7.4", - "buildDepConfigs": {}, - "portRef": "cargo_binstall_ghrel@0.1.0", - "specifiedVersion": false - }, - "rust_rustup": { - "version": "1.79.0", - "buildDepConfigs": { - "rustup_rustlang": { - "version": "1.27.1", - "buildDepConfigs": { - "git_aa": { - "version": "2.34.1", - "buildDepConfigs": {}, - "portRef": "git_aa@0.1.0", - "specifiedVersion": false - } - }, - "portRef": "rustup_rustlang@0.1.0", - "specifiedVersion": false - } - }, - "portRef": "rust_rustup@0.1.0", - "profile": "minimal", - "specifiedVersion": true - } - }, - "portRef": "cargobi_cratesio@0.1.0", - "crateName": "whiz", - "locked": true, - "specifiedVersion": false - }, - "bciqdm4ezstna7wryjatl75thl72622466u4ht2y25dkhvgu76kcnemi": { - "version": "1.79.0", - "buildDepConfigs": { - "rustup_rustlang": { - "version": "1.27.1", - "buildDepConfigs": { - "git_aa": { - "version": "2.34.1", - "buildDepConfigs": {}, - "portRef": "git_aa@0.1.0", - "specifiedVersion": false - } - }, - "portRef": "rustup_rustlang@0.1.0", - "specifiedVersion": false - } - }, - "portRef": "rust_rustup@0.1.0", - "profile": "minimal", - "specifiedVersion": true - }, - "bciqg6addm4t4fpqgkcpukqvbdrkscd56popl3at6aljs4tfnsfl3iki": { - "version": "0.3.4", - "buildDepConfigs": { - "cargo_binstall_ghrel": { - "version": "v1.7.4", - "buildDepConfigs": {}, - "portRef": "cargo_binstall_ghrel@0.1.0", - "specifiedVersion": false - }, - "rust_rustup": { - "version": "1.79.0", - "buildDepConfigs": { - "rustup_rustlang": { - "version": "1.27.1", - "buildDepConfigs": { - "git_aa": { - "version": "2.34.1", - "buildDepConfigs": {}, - "portRef": "git_aa@0.1.0", - "specifiedVersion": false - } - }, - "portRef": "rustup_rustlang@0.1.0", - "specifiedVersion": false - } - }, - "portRef": "rust_rustup@0.1.0", - "profile": "minimal", - "specifiedVersion": true - } - }, - "portRef": "cargobi_cratesio@0.1.0", - "crateName": "wit-deps-cli", - "locked": true, - "specifiedVersion": false - }, - "bciqjt35de5uwbp3qv3idkj7or2pu2gcun5jjkdttfupyuseslmjny5i": { - "version": "2.4.0", - "buildDepConfigs": { - "cargo_binstall_ghrel": { - "version": "v1.7.4", - "buildDepConfigs": {}, - "portRef": "cargo_binstall_ghrel@0.1.0", - "specifiedVersion": false - }, - "rust_rustup": { - "version": "1.79.0", - "buildDepConfigs": { - "rustup_rustlang": { - "version": "1.27.1", - "buildDepConfigs": { - "git_aa": { - "version": "2.34.1", - "buildDepConfigs": {}, - "portRef": "git_aa@0.1.0", - "specifiedVersion": false - } - }, - "portRef": "rustup_rustlang@0.1.0", - "specifiedVersion": false - } - }, - "portRef": "rust_rustup@0.1.0", - "profile": "minimal", - "specifiedVersion": true - } - }, - "portRef": "cargobi_cratesio@0.1.0", - "crateName": "git-cliff", - "locked": true, - "specifiedVersion": false - }, - "bciqnvknjnd6ivod4sz4usxxbxmhhyp4kiahvyy4yn6erblbzfu2s7na": { - "version": "1.33.0", - "buildDepConfigs": { - "cargo_binstall_ghrel": { - "version": "v1.7.4", - "buildDepConfigs": {}, - "portRef": "cargo_binstall_ghrel@0.1.0", - "specifiedVersion": false - }, - "rust_rustup": { - "version": "1.79.0", - "buildDepConfigs": { - "rustup_rustlang": { - "version": "1.27.1", - "buildDepConfigs": { - "git_aa": { - "version": "2.34.1", - "buildDepConfigs": {}, - "portRef": "git_aa@0.1.0", - "specifiedVersion": false - } - }, - "portRef": "rustup_rustlang@0.1.0", - "specifiedVersion": false - } - }, - "portRef": "rust_rustup@0.1.0", - "profile": "minimal", - "specifiedVersion": true - } - }, - "portRef": "cargobi_cratesio@0.1.0", - "crateName": "cargo-insta", - "locked": true, - "specifiedVersion": true - }, - "bciqgustga36b3bmjpi76xcde43z3efxo2czwmvotcxlktxkhcizywiq": { - "version": "0.2.5", - "buildDepConfigs": { - "cargo_binstall_ghrel": { - "version": "v1.7.4", - "buildDepConfigs": {}, - "portRef": "cargo_binstall_ghrel@0.1.0", - "specifiedVersion": false - }, - "rust_rustup": { - "version": "1.79.0", - "buildDepConfigs": { - "rustup_rustlang": { - "version": "1.27.1", - "buildDepConfigs": { - "git_aa": { - "version": "2.34.1", - "buildDepConfigs": {}, - "portRef": "git_aa@0.1.0", - "specifiedVersion": false - } - }, - "portRef": "rustup_rustlang@0.1.0", - "specifiedVersion": false - } - }, - "portRef": "rust_rustup@0.1.0", - "profile": "minimal", - "specifiedVersion": true - } - }, - "portRef": "cargobi_cratesio@0.1.0", - "crateName": "cross", - "locked": true, - "specifiedVersion": true - }, - "bciqow7vi5yxnhumjr3am67ct5pnavar2e55wp2vlddesl43oyppw5kq": { - "version": "0.116.1", - "buildDepConfigs": { - "cargo_binstall_ghrel": { - "version": "v1.7.4", - "buildDepConfigs": {}, - "portRef": "cargo_binstall_ghrel@0.1.0", - "specifiedVersion": false - }, - "rust_rustup": { - "version": "1.79.0", - "buildDepConfigs": { - "rustup_rustlang": { - "version": "1.27.1", - "buildDepConfigs": { - "git_aa": { - "version": "2.34.1", - "buildDepConfigs": {}, - "portRef": "git_aa@0.1.0", - "specifiedVersion": false - } - }, - "portRef": "rustup_rustlang@0.1.0", - "specifiedVersion": false - } - }, - "portRef": "rust_rustup@0.1.0", - "profile": "minimal", - "specifiedVersion": true - } - }, - "portRef": "cargobi_cratesio@0.1.0", - "crateName": "wasm-opt", - "locked": true, - "specifiedVersion": true - }, - "bciqgrinxolr2jfyj4smh3w5vwz6n4aacistocxbat5pz2fwf23glslq": { - "version": "1.208.1", - "buildDepConfigs": { - "cargo_binstall_ghrel": { - "version": "v1.7.4", - "buildDepConfigs": {}, - "portRef": "cargo_binstall_ghrel@0.1.0", - "specifiedVersion": false - }, - "rust_rustup": { - "version": "1.79.0", - "buildDepConfigs": { - "rustup_rustlang": { - "version": "1.27.1", - "buildDepConfigs": { - "git_aa": { - "version": "2.34.1", - "buildDepConfigs": {}, - "portRef": "git_aa@0.1.0", - "specifiedVersion": false - } - }, - "portRef": "rustup_rustlang@0.1.0", - "specifiedVersion": false - } - }, - "portRef": "rust_rustup@0.1.0", - "profile": "minimal", - "specifiedVersion": true - } - }, - "portRef": "cargobi_cratesio@0.1.0", - "crateName": "wasm-tools", - "locked": true, - "specifiedVersion": true - }, "bciqe6sahnduk5kofaohmecai2ip7ipjakg7rn32ezstllzr5nixmhyi": { "version": "nightly-2024-05-26", "buildDepConfigs": { @@ -963,7 +640,7 @@ "version": "1.27.1", "buildDepConfigs": { "git_aa": { - "version": "2.34.1", + "version": "2.46.0", "buildDepConfigs": {}, "portRef": "git_aa@0.1.0", "specifiedVersion": false @@ -981,7 +658,7 @@ "version": "0.1.47", "buildDepConfigs": { "cargo_binstall_ghrel": { - "version": "v1.7.4", + "version": "v1.10.2", "buildDepConfigs": {}, "portRef": "cargo_binstall_ghrel@0.1.0", "specifiedVersion": false @@ -993,7 +670,7 @@ "version": "1.27.1", "buildDepConfigs": { "git_aa": { - "version": "2.34.1", + "version": "2.46.0", "buildDepConfigs": {}, "portRef": "git_aa@0.1.0", "specifiedVersion": false @@ -1012,34 +689,6 @@ "crateName": "cargo-udeps", "locked": true, "specifiedVersion": true - }, - "bciqe6tpdv6hffdbc7hql52w3ivpdls47lgpuhsa3hzsryrwx7ty5dgy": { - "version": "3.30.1", - "buildDepConfigs": { - "cpy_bs_ghrel": { - "version": "3.12.2", - "buildDepConfigs": { - "tar_aa": { - "version": "1.34", - "buildDepConfigs": {}, - "portRef": "tar_aa@0.1.0", - "specifiedVersion": false - }, - "zstd_aa": { - "version": "v1.4.8,", - "buildDepConfigs": {}, - "portRef": "zstd_aa@0.1.0", - "specifiedVersion": false - } - }, - "portRef": "cpy_bs_ghrel@0.1.0", - "releaseTag": "20240224", - "specifiedVersion": true - } - }, - "portRef": "pipi_pypi@0.1.0", - "packageName": "cmake", - "specifiedVersion": false } } }, @@ -1058,207 +707,207 @@ "sets": { "ghjkEnvProvInstSet___main": { "installs": [ - "bciqfhtizxh3hxqyszx4cnvcfjqq5t3ox2vr7yekhywsmrcvrau3iqvi" + "bciqd5yzu6oghbke7anippco6lr6sgw2mv3c2esojkr3ta6f4ljwwuya" ], - "allowedBuildDeps": "bciqfvoetwxha3e26koqnilar26rz4ikr2m4rst73oz7swrw6557kdba" + "allowedBuildDeps": "bciqgdwpugwdrwifr6qqi3m6vs4y55edt7xkojyxk5zr5m3joowsgkki" }, "ghjkEnvProvInstSet____rust": { "installs": [ - "bciqd7mu5umyjjnt7v6lmyg4tlhj4bg6oq36iepo42sog2cxfs7tdpdi", - "bciqp5sktkn6etkn3z4kvheorj42bloeldjnv75glh6q4jfju4emqt3y", - "bciqfhtizxh3hxqyszx4cnvcfjqq5t3ox2vr7yekhywsmrcvrau3iqvi" + "bciqj5xfgcxzfw3tusoy4v53dcinxqxskfwe3lylcjevxl6mbroky5za", + "bciqlubbahrp4pxohyffmn5yj52atjgmn5nxepmkdev6wtmvpbx7kr7y", + "bciqd5yzu6oghbke7anippco6lr6sgw2mv3c2esojkr3ta6f4ljwwuya" ], - "allowedBuildDeps": "bciqfvoetwxha3e26koqnilar26rz4ikr2m4rst73oz7swrw6557kdba" + "allowedBuildDeps": "bciqgdwpugwdrwifr6qqi3m6vs4y55edt7xkojyxk5zr5m3joowsgkki" }, "ghjkEnvProvInstSet___dev": { "installs": [ - "bciqddqzemyb6kw24f2nhhy7hyfo62ytfjllvdnfhurqhb7qqlvo4noa", - "bciql6pq2bc6uxp7z5luxf3nn44bx3vdqo7fsz6sonammtbpwsdmwxcy", - "bciqllhtecyu5p4isjgutrvnb2xgkrryhxy6ooacn5p6ol2rriku6zwy", - "bciqfbafead64bhnmec2sxcnc7v4c4m4p4pgehp5fjihkwqxkzkldmoi", - "bciqn62icb2lxjafe2jzh53frwkycch2tk22lhk5aqkhsdl7cwjbpmji", - "bciqjq7sv663snw57fsybwrfefbjibkn6t4psfbd2r3dodboafhr6lmq", - "bciqpqesmcpthtkaswi5pb25yqmdea674er4cz7nl2fsa6lsii6ludwa", - "bciqctytzcjsv7qqv7kynxwfniayjlocaigb2aihikhlrgt5lab3p32i", - "bciqm2woxgqgrhy7aa2mqhrbkce7o7xs65iabadfpqyh3xqre5glgsaa", - "bciqd7mu5umyjjnt7v6lmyg4tlhj4bg6oq36iepo42sog2cxfs7tdpdi", - "bciqp5sktkn6etkn3z4kvheorj42bloeldjnv75glh6q4jfju4emqt3y", - "bciqfhtizxh3hxqyszx4cnvcfjqq5t3ox2vr7yekhywsmrcvrau3iqvi", - "bciqlajnywjssd2yeb5a2p4qp7was3au33ekjcvashkvei6zr7mdh2dq", - "bciqgi2hzel6ndyvbd36ovvcpdv4ltizav3ibmqnujzdp4zzf6zai3vi", - "bciqhuc6rcywoh677qmscqjbdgofoyalurlnabho5amqmhdfpilrgc5a", - "bciqkdrtbzm4whp2wklha6js3632cew6reewqcyqt7ordcwg4nfedghi", - "bciqewnlpjw47a2lbkd2qca6jf6opnreaizhblcg5omt2uglljw2onli", - "bciqg3ul5otgtisml3egxsjfe6kvg2jsvl5zakibnaxgiqe2jzv6amei", - "bciqgjly7ijjp5zh5ytt2jcpptqx5vmbgs7lrvn27pujnze6dtljgt6y", - "bciqgnqqxpmenmhf5y7psrovuhwdeqh5x5teil2tkggvpgowac3hgjbq", - "bciqh2qn4zwqv7ixf3vb4jzgy2nfzxzz6va5wu5uzv3vtjaqpgg2tygi", - "bciqdy7ppzs3sotxid2juepk2s7xg7a4g76amjzfeb5ec6kwnwmfnhaq" + "bciqbx637744bfiyvprs77xdnvdt7uuwmtlntfjpwmkda672gklkbpmi", + "bciqdoqocirh7aseu5o5hfqaj3sb3pfd3z3rlvig26xttmcmsoljuz6i", + "bciqjsjvkjm6xvoovs6y3y6me32422zr5wc5njs4kwfmmyf6nt6jzv2i", + "bciqdf7jtq3zzhn676t2dr7fyve47fj7coajtymmye353lrrluskjk7q", + "bciqeaqeduyhykw7s7gq6ney6ci7deheq3etgdwkvg55mwbzdhz2opra", + "bciqdtuhf425g6prb5fyupbcokttmkill6wyqk7bkphx3ueltl5mvu4q", + "bciqmvgsg7h3ohj3m7das4bznahgt6tyq7mamta3n2vorulqvml7mywq", + "bciqicdqw36v63cbrscwsgtu2htrmwmgtfoxexv4rx5d2y24vytxbuma", + "bciqe33uhsuaesrjk6luzxrbbimwg5ydt6x2lrieelwbr7aft4g2qwsy", + "bciqj5xfgcxzfw3tusoy4v53dcinxqxskfwe3lylcjevxl6mbroky5za", + "bciqlubbahrp4pxohyffmn5yj52atjgmn5nxepmkdev6wtmvpbx7kr7y", + "bciqd5yzu6oghbke7anippco6lr6sgw2mv3c2esojkr3ta6f4ljwwuya", + "bciqgpwvq6qf34xolzh65zr3v5dpyhahtybei46nnjmy2sitnoixwhsa", + "bciqkgncxbauys2qfguplxcz2auxrcyamj4b6htqk2fqvohfm3afd7sa", + "bciqh7qqm2bohoswnwjywael2m3f6xn4n6ceifyw4usrmaahjioldq6a", + "bciqezep4ufkgwesldlm5etyfkgdsiickfudx7cosydcz6xtgeorn2hy", + "bciqaixkkacuuligsvtjcfdfgjgl65owtyspiiljb3vmutlgymecsiwq", + "bciqlt27ioikxnpkqq37hma7ibn5e5wpzfarbvoh77zwdkarwghtvzxa", + "bciqojan3zglnfctnmqyxvnxaha46yrnlhj77j3kw4mxadvauqepqdba", + "bciqcnbruy2q6trpvia52n2yis4t27taoz4mxkeguqz5aif7ex6rp26y", + "bciqpu7gxs3zm7i4gwp3m3cfdxwz27ixvsykdnbxrl5m5mt3xbb3b4la", + "bciqjme7csfq43oenkrsakdhaha34hgy6vdwkfffki2ank3kf6mjcguq" ], - "allowedBuildDeps": "bciqfvoetwxha3e26koqnilar26rz4ikr2m4rst73oz7swrw6557kdba" + "allowedBuildDeps": "bciqgdwpugwdrwifr6qqi3m6vs4y55edt7xkojyxk5zr5m3joowsgkki" }, "ghjkEnvProvInstSet____ecma": { "installs": [ - "bciqkdrtbzm4whp2wklha6js3632cew6reewqcyqt7ordcwg4nfedghi", - "bciqewnlpjw47a2lbkd2qca6jf6opnreaizhblcg5omt2uglljw2onli", - "bciqg3ul5otgtisml3egxsjfe6kvg2jsvl5zakibnaxgiqe2jzv6amei", - "bciqfhtizxh3hxqyszx4cnvcfjqq5t3ox2vr7yekhywsmrcvrau3iqvi" + "bciqezep4ufkgwesldlm5etyfkgdsiickfudx7cosydcz6xtgeorn2hy", + "bciqaixkkacuuligsvtjcfdfgjgl65owtyspiiljb3vmutlgymecsiwq", + "bciqlt27ioikxnpkqq37hma7ibn5e5wpzfarbvoh77zwdkarwghtvzxa", + "bciqd5yzu6oghbke7anippco6lr6sgw2mv3c2esojkr3ta6f4ljwwuya" ], - "allowedBuildDeps": "bciqfvoetwxha3e26koqnilar26rz4ikr2m4rst73oz7swrw6557kdba" + "allowedBuildDeps": "bciqgdwpugwdrwifr6qqi3m6vs4y55edt7xkojyxk5zr5m3joowsgkki" }, "ghjkEnvProvInstSet____python": { "installs": [ - "bciqlajnywjssd2yeb5a2p4qp7was3au33ekjcvashkvei6zr7mdh2dq", - "bciqgi2hzel6ndyvbd36ovvcpdv4ltizav3ibmqnujzdp4zzf6zai3vi", - "bciqhuc6rcywoh677qmscqjbdgofoyalurlnabho5amqmhdfpilrgc5a", - "bciqfhtizxh3hxqyszx4cnvcfjqq5t3ox2vr7yekhywsmrcvrau3iqvi" + "bciqgpwvq6qf34xolzh65zr3v5dpyhahtybei46nnjmy2sitnoixwhsa", + "bciqkgncxbauys2qfguplxcz2auxrcyamj4b6htqk2fqvohfm3afd7sa", + "bciqh7qqm2bohoswnwjywael2m3f6xn4n6ceifyw4usrmaahjioldq6a", + "bciqd5yzu6oghbke7anippco6lr6sgw2mv3c2esojkr3ta6f4ljwwuya" ], - "allowedBuildDeps": "bciqfvoetwxha3e26koqnilar26rz4ikr2m4rst73oz7swrw6557kdba" + "allowedBuildDeps": "bciqgdwpugwdrwifr6qqi3m6vs4y55edt7xkojyxk5zr5m3joowsgkki" }, "ghjkEnvProvInstSet____wasm": { "installs": [ - "bciqgjly7ijjp5zh5ytt2jcpptqx5vmbgs7lrvn27pujnze6dtljgt6y", - "bciqgnqqxpmenmhf5y7psrovuhwdeqh5x5teil2tkggvpgowac3hgjbq", - "bciqh2qn4zwqv7ixf3vb4jzgy2nfzxzz6va5wu5uzv3vtjaqpgg2tygi", - "bciqdy7ppzs3sotxid2juepk2s7xg7a4g76amjzfeb5ec6kwnwmfnhaq", - "bciqfhtizxh3hxqyszx4cnvcfjqq5t3ox2vr7yekhywsmrcvrau3iqvi" + "bciqojan3zglnfctnmqyxvnxaha46yrnlhj77j3kw4mxadvauqepqdba", + "bciqcnbruy2q6trpvia52n2yis4t27taoz4mxkeguqz5aif7ex6rp26y", + "bciqpu7gxs3zm7i4gwp3m3cfdxwz27ixvsykdnbxrl5m5mt3xbb3b4la", + "bciqjme7csfq43oenkrsakdhaha34hgy6vdwkfffki2ank3kf6mjcguq", + "bciqd5yzu6oghbke7anippco6lr6sgw2mv3c2esojkr3ta6f4ljwwuya" ], - "allowedBuildDeps": "bciqfvoetwxha3e26koqnilar26rz4ikr2m4rst73oz7swrw6557kdba" + "allowedBuildDeps": "bciqgdwpugwdrwifr6qqi3m6vs4y55edt7xkojyxk5zr5m3joowsgkki" }, "ghjkEnvProvInstSet___ci": { "installs": [ - "bciqjq7sv663snw57fsybwrfefbjibkn6t4psfbd2r3dodboafhr6lmq", - "bciqpqesmcpthtkaswi5pb25yqmdea674er4cz7nl2fsa6lsii6ludwa", - "bciqctytzcjsv7qqv7kynxwfniayjlocaigb2aihikhlrgt5lab3p32i", - "bciqm2woxgqgrhy7aa2mqhrbkce7o7xs65iabadfpqyh3xqre5glgsaa", - "bciqd7mu5umyjjnt7v6lmyg4tlhj4bg6oq36iepo42sog2cxfs7tdpdi", - "bciqp5sktkn6etkn3z4kvheorj42bloeldjnv75glh6q4jfju4emqt3y", - "bciqfhtizxh3hxqyszx4cnvcfjqq5t3ox2vr7yekhywsmrcvrau3iqvi", - "bciqlajnywjssd2yeb5a2p4qp7was3au33ekjcvashkvei6zr7mdh2dq", - "bciqgi2hzel6ndyvbd36ovvcpdv4ltizav3ibmqnujzdp4zzf6zai3vi", - "bciqhuc6rcywoh677qmscqjbdgofoyalurlnabho5amqmhdfpilrgc5a", - "bciqkdrtbzm4whp2wklha6js3632cew6reewqcyqt7ordcwg4nfedghi", - "bciqewnlpjw47a2lbkd2qca6jf6opnreaizhblcg5omt2uglljw2onli", - "bciqg3ul5otgtisml3egxsjfe6kvg2jsvl5zakibnaxgiqe2jzv6amei", - "bciqgjly7ijjp5zh5ytt2jcpptqx5vmbgs7lrvn27pujnze6dtljgt6y", - "bciqgnqqxpmenmhf5y7psrovuhwdeqh5x5teil2tkggvpgowac3hgjbq", - "bciqh2qn4zwqv7ixf3vb4jzgy2nfzxzz6va5wu5uzv3vtjaqpgg2tygi", - "bciqdy7ppzs3sotxid2juepk2s7xg7a4g76amjzfeb5ec6kwnwmfnhaq" + "bciqdtuhf425g6prb5fyupbcokttmkill6wyqk7bkphx3ueltl5mvu4q", + "bciqmvgsg7h3ohj3m7das4bznahgt6tyq7mamta3n2vorulqvml7mywq", + "bciqicdqw36v63cbrscwsgtu2htrmwmgtfoxexv4rx5d2y24vytxbuma", + "bciqe33uhsuaesrjk6luzxrbbimwg5ydt6x2lrieelwbr7aft4g2qwsy", + "bciqj5xfgcxzfw3tusoy4v53dcinxqxskfwe3lylcjevxl6mbroky5za", + "bciqlubbahrp4pxohyffmn5yj52atjgmn5nxepmkdev6wtmvpbx7kr7y", + "bciqd5yzu6oghbke7anippco6lr6sgw2mv3c2esojkr3ta6f4ljwwuya", + "bciqgpwvq6qf34xolzh65zr3v5dpyhahtybei46nnjmy2sitnoixwhsa", + "bciqkgncxbauys2qfguplxcz2auxrcyamj4b6htqk2fqvohfm3afd7sa", + "bciqh7qqm2bohoswnwjywael2m3f6xn4n6ceifyw4usrmaahjioldq6a", + "bciqezep4ufkgwesldlm5etyfkgdsiickfudx7cosydcz6xtgeorn2hy", + "bciqaixkkacuuligsvtjcfdfgjgl65owtyspiiljb3vmutlgymecsiwq", + "bciqlt27ioikxnpkqq37hma7ibn5e5wpzfarbvoh77zwdkarwghtvzxa", + "bciqojan3zglnfctnmqyxvnxaha46yrnlhj77j3kw4mxadvauqepqdba", + "bciqcnbruy2q6trpvia52n2yis4t27taoz4mxkeguqz5aif7ex6rp26y", + "bciqpu7gxs3zm7i4gwp3m3cfdxwz27ixvsykdnbxrl5m5mt3xbb3b4la", + "bciqjme7csfq43oenkrsakdhaha34hgy6vdwkfffki2ank3kf6mjcguq" ], - "allowedBuildDeps": "bciqfvoetwxha3e26koqnilar26rz4ikr2m4rst73oz7swrw6557kdba" + "allowedBuildDeps": "bciqgdwpugwdrwifr6qqi3m6vs4y55edt7xkojyxk5zr5m3joowsgkki" }, "ghjkEnvProvInstSet_______task_env_lint-udeps": { "installs": [ - "bciqbrqcdc7q732b2hsphpf7t5rzhvz6h5dfqxfxs345t62asad7zcry", - "bciqjcqwnurxnykxkfkbd7g755fcauqhf4lv5voo5nn62afj3tybfesi", - "bciqfhtizxh3hxqyszx4cnvcfjqq5t3ox2vr7yekhywsmrcvrau3iqvi" + "bciqezkigmtx5tweeflmn27yofgwybmgbat7g6jg4xmxamomsdpvenay", + "bciqiknd2vbwhxng2oy5d7qjpor7jq74pulaeijfrywyggv4mw3wngsi", + "bciqd5yzu6oghbke7anippco6lr6sgw2mv3c2esojkr3ta6f4ljwwuya" ], - "allowedBuildDeps": "bciqfvoetwxha3e26koqnilar26rz4ikr2m4rst73oz7swrw6557kdba" + "allowedBuildDeps": "bciqgdwpugwdrwifr6qqi3m6vs4y55edt7xkojyxk5zr5m3joowsgkki" }, "ghjkEnvProvInstSet_______task_env_dev-website": { "installs": [ - "bciqkdrtbzm4whp2wklha6js3632cew6reewqcyqt7ordcwg4nfedghi", - "bciqewnlpjw47a2lbkd2qca6jf6opnreaizhblcg5omt2uglljw2onli", - "bciqg3ul5otgtisml3egxsjfe6kvg2jsvl5zakibnaxgiqe2jzv6amei", - "bciqfhtizxh3hxqyszx4cnvcfjqq5t3ox2vr7yekhywsmrcvrau3iqvi", - "bciqlajnywjssd2yeb5a2p4qp7was3au33ekjcvashkvei6zr7mdh2dq", - "bciqgi2hzel6ndyvbd36ovvcpdv4ltizav3ibmqnujzdp4zzf6zai3vi", - "bciqhuc6rcywoh677qmscqjbdgofoyalurlnabho5amqmhdfpilrgc5a" + "bciqezep4ufkgwesldlm5etyfkgdsiickfudx7cosydcz6xtgeorn2hy", + "bciqaixkkacuuligsvtjcfdfgjgl65owtyspiiljb3vmutlgymecsiwq", + "bciqlt27ioikxnpkqq37hma7ibn5e5wpzfarbvoh77zwdkarwghtvzxa", + "bciqd5yzu6oghbke7anippco6lr6sgw2mv3c2esojkr3ta6f4ljwwuya", + "bciqgpwvq6qf34xolzh65zr3v5dpyhahtybei46nnjmy2sitnoixwhsa", + "bciqkgncxbauys2qfguplxcz2auxrcyamj4b6htqk2fqvohfm3afd7sa", + "bciqh7qqm2bohoswnwjywael2m3f6xn4n6ceifyw4usrmaahjioldq6a" ], - "allowedBuildDeps": "bciqfvoetwxha3e26koqnilar26rz4ikr2m4rst73oz7swrw6557kdba" + "allowedBuildDeps": "bciqgdwpugwdrwifr6qqi3m6vs4y55edt7xkojyxk5zr5m3joowsgkki" }, "ghjkEnvProvInstSet_______task_env_test-rust": { "installs": [ - "bciqd7mu5umyjjnt7v6lmyg4tlhj4bg6oq36iepo42sog2cxfs7tdpdi", - "bciqp5sktkn6etkn3z4kvheorj42bloeldjnv75glh6q4jfju4emqt3y", - "bciqfhtizxh3hxqyszx4cnvcfjqq5t3ox2vr7yekhywsmrcvrau3iqvi", - "bciqkdrtbzm4whp2wklha6js3632cew6reewqcyqt7ordcwg4nfedghi", - "bciqewnlpjw47a2lbkd2qca6jf6opnreaizhblcg5omt2uglljw2onli", - "bciqg3ul5otgtisml3egxsjfe6kvg2jsvl5zakibnaxgiqe2jzv6amei", - "bciqlajnywjssd2yeb5a2p4qp7was3au33ekjcvashkvei6zr7mdh2dq", - "bciqgi2hzel6ndyvbd36ovvcpdv4ltizav3ibmqnujzdp4zzf6zai3vi", - "bciqhuc6rcywoh677qmscqjbdgofoyalurlnabho5amqmhdfpilrgc5a" + "bciqj5xfgcxzfw3tusoy4v53dcinxqxskfwe3lylcjevxl6mbroky5za", + "bciqlubbahrp4pxohyffmn5yj52atjgmn5nxepmkdev6wtmvpbx7kr7y", + "bciqd5yzu6oghbke7anippco6lr6sgw2mv3c2esojkr3ta6f4ljwwuya", + "bciqezep4ufkgwesldlm5etyfkgdsiickfudx7cosydcz6xtgeorn2hy", + "bciqaixkkacuuligsvtjcfdfgjgl65owtyspiiljb3vmutlgymecsiwq", + "bciqlt27ioikxnpkqq37hma7ibn5e5wpzfarbvoh77zwdkarwghtvzxa", + "bciqgpwvq6qf34xolzh65zr3v5dpyhahtybei46nnjmy2sitnoixwhsa", + "bciqkgncxbauys2qfguplxcz2auxrcyamj4b6htqk2fqvohfm3afd7sa", + "bciqh7qqm2bohoswnwjywael2m3f6xn4n6ceifyw4usrmaahjioldq6a" ], - "allowedBuildDeps": "bciqfvoetwxha3e26koqnilar26rz4ikr2m4rst73oz7swrw6557kdba" + "allowedBuildDeps": "bciqgdwpugwdrwifr6qqi3m6vs4y55edt7xkojyxk5zr5m3joowsgkki" }, "ghjkEnvProvInstSet_______task_env_dev-eg-tgraphs": { "installs": [ - "bciqd7mu5umyjjnt7v6lmyg4tlhj4bg6oq36iepo42sog2cxfs7tdpdi", - "bciqp5sktkn6etkn3z4kvheorj42bloeldjnv75glh6q4jfju4emqt3y", - "bciqfhtizxh3hxqyszx4cnvcfjqq5t3ox2vr7yekhywsmrcvrau3iqvi", - "bciqkdrtbzm4whp2wklha6js3632cew6reewqcyqt7ordcwg4nfedghi", - "bciqewnlpjw47a2lbkd2qca6jf6opnreaizhblcg5omt2uglljw2onli", - "bciqg3ul5otgtisml3egxsjfe6kvg2jsvl5zakibnaxgiqe2jzv6amei", - "bciqlajnywjssd2yeb5a2p4qp7was3au33ekjcvashkvei6zr7mdh2dq", - "bciqgi2hzel6ndyvbd36ovvcpdv4ltizav3ibmqnujzdp4zzf6zai3vi", - "bciqhuc6rcywoh677qmscqjbdgofoyalurlnabho5amqmhdfpilrgc5a" + "bciqj5xfgcxzfw3tusoy4v53dcinxqxskfwe3lylcjevxl6mbroky5za", + "bciqlubbahrp4pxohyffmn5yj52atjgmn5nxepmkdev6wtmvpbx7kr7y", + "bciqd5yzu6oghbke7anippco6lr6sgw2mv3c2esojkr3ta6f4ljwwuya", + "bciqezep4ufkgwesldlm5etyfkgdsiickfudx7cosydcz6xtgeorn2hy", + "bciqaixkkacuuligsvtjcfdfgjgl65owtyspiiljb3vmutlgymecsiwq", + "bciqlt27ioikxnpkqq37hma7ibn5e5wpzfarbvoh77zwdkarwghtvzxa", + "bciqgpwvq6qf34xolzh65zr3v5dpyhahtybei46nnjmy2sitnoixwhsa", + "bciqkgncxbauys2qfguplxcz2auxrcyamj4b6htqk2fqvohfm3afd7sa", + "bciqh7qqm2bohoswnwjywael2m3f6xn4n6ceifyw4usrmaahjioldq6a" ], - "allowedBuildDeps": "bciqfvoetwxha3e26koqnilar26rz4ikr2m4rst73oz7swrw6557kdba" + "allowedBuildDeps": "bciqgdwpugwdrwifr6qqi3m6vs4y55edt7xkojyxk5zr5m3joowsgkki" }, "ghjkEnvProvInstSet_______task_env_build-tgraph-core": { "installs": [ - "bciqd7mu5umyjjnt7v6lmyg4tlhj4bg6oq36iepo42sog2cxfs7tdpdi", - "bciqp5sktkn6etkn3z4kvheorj42bloeldjnv75glh6q4jfju4emqt3y", - "bciqfhtizxh3hxqyszx4cnvcfjqq5t3ox2vr7yekhywsmrcvrau3iqvi", - "bciqgjly7ijjp5zh5ytt2jcpptqx5vmbgs7lrvn27pujnze6dtljgt6y", - "bciqgnqqxpmenmhf5y7psrovuhwdeqh5x5teil2tkggvpgowac3hgjbq", - "bciqh2qn4zwqv7ixf3vb4jzgy2nfzxzz6va5wu5uzv3vtjaqpgg2tygi", - "bciqdy7ppzs3sotxid2juepk2s7xg7a4g76amjzfeb5ec6kwnwmfnhaq" + "bciqj5xfgcxzfw3tusoy4v53dcinxqxskfwe3lylcjevxl6mbroky5za", + "bciqlubbahrp4pxohyffmn5yj52atjgmn5nxepmkdev6wtmvpbx7kr7y", + "bciqd5yzu6oghbke7anippco6lr6sgw2mv3c2esojkr3ta6f4ljwwuya", + "bciqojan3zglnfctnmqyxvnxaha46yrnlhj77j3kw4mxadvauqepqdba", + "bciqcnbruy2q6trpvia52n2yis4t27taoz4mxkeguqz5aif7ex6rp26y", + "bciqpu7gxs3zm7i4gwp3m3cfdxwz27ixvsykdnbxrl5m5mt3xbb3b4la", + "bciqjme7csfq43oenkrsakdhaha34hgy6vdwkfffki2ank3kf6mjcguq" ], - "allowedBuildDeps": "bciqfvoetwxha3e26koqnilar26rz4ikr2m4rst73oz7swrw6557kdba" + "allowedBuildDeps": "bciqgdwpugwdrwifr6qqi3m6vs4y55edt7xkojyxk5zr5m3joowsgkki" }, "ghjkEnvProvInstSet_______task_env_build-tgraph-py": { "installs": [ - "bciqd7mu5umyjjnt7v6lmyg4tlhj4bg6oq36iepo42sog2cxfs7tdpdi", - "bciqp5sktkn6etkn3z4kvheorj42bloeldjnv75glh6q4jfju4emqt3y", - "bciqfhtizxh3hxqyszx4cnvcfjqq5t3ox2vr7yekhywsmrcvrau3iqvi", - "bciqgjly7ijjp5zh5ytt2jcpptqx5vmbgs7lrvn27pujnze6dtljgt6y", - "bciqgnqqxpmenmhf5y7psrovuhwdeqh5x5teil2tkggvpgowac3hgjbq", - "bciqh2qn4zwqv7ixf3vb4jzgy2nfzxzz6va5wu5uzv3vtjaqpgg2tygi", - "bciqdy7ppzs3sotxid2juepk2s7xg7a4g76amjzfeb5ec6kwnwmfnhaq", - "bciqlajnywjssd2yeb5a2p4qp7was3au33ekjcvashkvei6zr7mdh2dq", - "bciqgi2hzel6ndyvbd36ovvcpdv4ltizav3ibmqnujzdp4zzf6zai3vi", - "bciqhuc6rcywoh677qmscqjbdgofoyalurlnabho5amqmhdfpilrgc5a" + "bciqj5xfgcxzfw3tusoy4v53dcinxqxskfwe3lylcjevxl6mbroky5za", + "bciqlubbahrp4pxohyffmn5yj52atjgmn5nxepmkdev6wtmvpbx7kr7y", + "bciqd5yzu6oghbke7anippco6lr6sgw2mv3c2esojkr3ta6f4ljwwuya", + "bciqojan3zglnfctnmqyxvnxaha46yrnlhj77j3kw4mxadvauqepqdba", + "bciqcnbruy2q6trpvia52n2yis4t27taoz4mxkeguqz5aif7ex6rp26y", + "bciqpu7gxs3zm7i4gwp3m3cfdxwz27ixvsykdnbxrl5m5mt3xbb3b4la", + "bciqjme7csfq43oenkrsakdhaha34hgy6vdwkfffki2ank3kf6mjcguq", + "bciqgpwvq6qf34xolzh65zr3v5dpyhahtybei46nnjmy2sitnoixwhsa", + "bciqkgncxbauys2qfguplxcz2auxrcyamj4b6htqk2fqvohfm3afd7sa", + "bciqh7qqm2bohoswnwjywael2m3f6xn4n6ceifyw4usrmaahjioldq6a" ], - "allowedBuildDeps": "bciqfvoetwxha3e26koqnilar26rz4ikr2m4rst73oz7swrw6557kdba" + "allowedBuildDeps": "bciqgdwpugwdrwifr6qqi3m6vs4y55edt7xkojyxk5zr5m3joowsgkki" }, "ghjkEnvProvInstSet_______task_env_build-tgraph-ts": { "installs": [ - "bciqd7mu5umyjjnt7v6lmyg4tlhj4bg6oq36iepo42sog2cxfs7tdpdi", - "bciqp5sktkn6etkn3z4kvheorj42bloeldjnv75glh6q4jfju4emqt3y", - "bciqfhtizxh3hxqyszx4cnvcfjqq5t3ox2vr7yekhywsmrcvrau3iqvi", - "bciqgjly7ijjp5zh5ytt2jcpptqx5vmbgs7lrvn27pujnze6dtljgt6y", - "bciqgnqqxpmenmhf5y7psrovuhwdeqh5x5teil2tkggvpgowac3hgjbq", - "bciqh2qn4zwqv7ixf3vb4jzgy2nfzxzz6va5wu5uzv3vtjaqpgg2tygi", - "bciqdy7ppzs3sotxid2juepk2s7xg7a4g76amjzfeb5ec6kwnwmfnhaq", - "bciqkdrtbzm4whp2wklha6js3632cew6reewqcyqt7ordcwg4nfedghi", - "bciqewnlpjw47a2lbkd2qca6jf6opnreaizhblcg5omt2uglljw2onli", - "bciqg3ul5otgtisml3egxsjfe6kvg2jsvl5zakibnaxgiqe2jzv6amei" + "bciqj5xfgcxzfw3tusoy4v53dcinxqxskfwe3lylcjevxl6mbroky5za", + "bciqlubbahrp4pxohyffmn5yj52atjgmn5nxepmkdev6wtmvpbx7kr7y", + "bciqd5yzu6oghbke7anippco6lr6sgw2mv3c2esojkr3ta6f4ljwwuya", + "bciqojan3zglnfctnmqyxvnxaha46yrnlhj77j3kw4mxadvauqepqdba", + "bciqcnbruy2q6trpvia52n2yis4t27taoz4mxkeguqz5aif7ex6rp26y", + "bciqpu7gxs3zm7i4gwp3m3cfdxwz27ixvsykdnbxrl5m5mt3xbb3b4la", + "bciqjme7csfq43oenkrsakdhaha34hgy6vdwkfffki2ank3kf6mjcguq", + "bciqezep4ufkgwesldlm5etyfkgdsiickfudx7cosydcz6xtgeorn2hy", + "bciqaixkkacuuligsvtjcfdfgjgl65owtyspiiljb3vmutlgymecsiwq", + "bciqlt27ioikxnpkqq37hma7ibn5e5wpzfarbvoh77zwdkarwghtvzxa" ], - "allowedBuildDeps": "bciqfvoetwxha3e26koqnilar26rz4ikr2m4rst73oz7swrw6557kdba" + "allowedBuildDeps": "bciqgdwpugwdrwifr6qqi3m6vs4y55edt7xkojyxk5zr5m3joowsgkki" }, "ghjkEnvProvInstSet_______task_env_build-sys-tgraphs": { "installs": [ - "bciqd7mu5umyjjnt7v6lmyg4tlhj4bg6oq36iepo42sog2cxfs7tdpdi", - "bciqp5sktkn6etkn3z4kvheorj42bloeldjnv75glh6q4jfju4emqt3y", - "bciqfhtizxh3hxqyszx4cnvcfjqq5t3ox2vr7yekhywsmrcvrau3iqvi", - "bciqlajnywjssd2yeb5a2p4qp7was3au33ekjcvashkvei6zr7mdh2dq", - "bciqgi2hzel6ndyvbd36ovvcpdv4ltizav3ibmqnujzdp4zzf6zai3vi", - "bciqhuc6rcywoh677qmscqjbdgofoyalurlnabho5amqmhdfpilrgc5a" + "bciqj5xfgcxzfw3tusoy4v53dcinxqxskfwe3lylcjevxl6mbroky5za", + "bciqlubbahrp4pxohyffmn5yj52atjgmn5nxepmkdev6wtmvpbx7kr7y", + "bciqd5yzu6oghbke7anippco6lr6sgw2mv3c2esojkr3ta6f4ljwwuya", + "bciqgpwvq6qf34xolzh65zr3v5dpyhahtybei46nnjmy2sitnoixwhsa", + "bciqkgncxbauys2qfguplxcz2auxrcyamj4b6htqk2fqvohfm3afd7sa", + "bciqh7qqm2bohoswnwjywael2m3f6xn4n6ceifyw4usrmaahjioldq6a" ], - "allowedBuildDeps": "bciqfvoetwxha3e26koqnilar26rz4ikr2m4rst73oz7swrw6557kdba" + "allowedBuildDeps": "bciqgdwpugwdrwifr6qqi3m6vs4y55edt7xkojyxk5zr5m3joowsgkki" }, "ghjkEnvProvInstSet___oci": { "installs": [ - "bciqd7mu5umyjjnt7v6lmyg4tlhj4bg6oq36iepo42sog2cxfs7tdpdi", - "bciqp5sktkn6etkn3z4kvheorj42bloeldjnv75glh6q4jfju4emqt3y", - "bciqfhtizxh3hxqyszx4cnvcfjqq5t3ox2vr7yekhywsmrcvrau3iqvi", - "bciqgjly7ijjp5zh5ytt2jcpptqx5vmbgs7lrvn27pujnze6dtljgt6y", - "bciqgnqqxpmenmhf5y7psrovuhwdeqh5x5teil2tkggvpgowac3hgjbq", - "bciqh2qn4zwqv7ixf3vb4jzgy2nfzxzz6va5wu5uzv3vtjaqpgg2tygi", - "bciqdy7ppzs3sotxid2juepk2s7xg7a4g76amjzfeb5ec6kwnwmfnhaq" + "bciqj5xfgcxzfw3tusoy4v53dcinxqxskfwe3lylcjevxl6mbroky5za", + "bciqlubbahrp4pxohyffmn5yj52atjgmn5nxepmkdev6wtmvpbx7kr7y", + "bciqd5yzu6oghbke7anippco6lr6sgw2mv3c2esojkr3ta6f4ljwwuya", + "bciqojan3zglnfctnmqyxvnxaha46yrnlhj77j3kw4mxadvauqepqdba", + "bciqcnbruy2q6trpvia52n2yis4t27taoz4mxkeguqz5aif7ex6rp26y", + "bciqpu7gxs3zm7i4gwp3m3cfdxwz27ixvsykdnbxrl5m5mt3xbb3b4la", + "bciqjme7csfq43oenkrsakdhaha34hgy6vdwkfffki2ank3kf6mjcguq" ], - "allowedBuildDeps": "bciqfvoetwxha3e26koqnilar26rz4ikr2m4rst73oz7swrw6557kdba" + "allowedBuildDeps": "bciqgdwpugwdrwifr6qqi3m6vs4y55edt7xkojyxk5zr5m3joowsgkki" } } } @@ -2091,7 +1740,7 @@ } ], "blackboard": { - "bciqfhtizxh3hxqyszx4cnvcfjqq5t3ox2vr7yekhywsmrcvrau3iqvi": { + "bciqd5yzu6oghbke7anippco6lr6sgw2mv3c2esojkr3ta6f4ljwwuya": { "version": "1.45.2", "port": { "ty": "denoWorker@v1", @@ -2105,7 +1754,7 @@ "x86_64-windows" ], "version": "0.1.0", - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/deno_ghrel.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/deno_ghrel.ts" } }, "bciqb6ua63xodzwxngnbjq35hfikiwzb3dclbqkc7e6xgjdt5jin4pia": { @@ -2238,7 +1887,7 @@ "portRef": "zstd_aa@0.1.0" } }, - "bciqhnfhxvpxn2lci2lbdtigj6i6wqzpqsmijykq2f7bebrpkyrnr7hq": { + "bciqbbs5l2j72vleufdba2nj4k4dnp3akxv4w74rhhpnz5buoixxnuqy": { "manifest": { "ty": "denoWorker@v1", "name": "rustup_rustlang", @@ -2263,13 +1912,13 @@ "name": "git_aa" } ], - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/rustup.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/rustup.ts" }, "defaultInst": { "portRef": "rustup_rustlang@0.1.0" } }, - "bciqmcbhawhqin3go4l3rywevxny7lfudhbpffdekhmg35isg7xehvmq": { + "bciqfn3guc53gpo32zezii4g6it32cbmb5xli7e47d2qvurnqinz7edq": { "manifest": { "ty": "denoWorker@v1", "name": "rust_rustup", @@ -2299,7 +1948,7 @@ "name": "rustup_rustlang" } ], - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/rust.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/rust.ts" }, "defaultInst": { "version": "1.79.0", @@ -2307,7 +1956,7 @@ "profile": "minimal" } }, - "bciqiiyn3yvadqrhgjmrk7ieqmrsuzd6vbcuvq63x6vpbepgvghhtjja": { + "bciqj25jnt3hzhwfm6z2tkpfyxt7cpb2dbwavf75doh5d5qnwwdugljy": { "manifest": { "ty": "denoWorker@v1", "name": "cargo_binstall_ghrel", @@ -2318,13 +1967,13 @@ "x86_64-darwin" ], "version": "0.1.0", - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/cargo-binstall.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/cargo-binstall.ts" }, "defaultInst": { "portRef": "cargo_binstall_ghrel@0.1.0" } }, - "bciqjhhjc4a4vtd7oeyh5ei7zxmztzkxr523h27uh7ps6rlw2r2w4ufi": { + "bciqfr2ubzsw3lefwt7mwuzlkct2cbwvo4l44hrrigfzx64uocww5rki": { "manifest": { "ty": "denoWorker@v1", "name": "pnpm_ghrel", @@ -2337,13 +1986,13 @@ "x86_64-windows" ], "version": "0.1.0", - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/pnpm.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/pnpm.ts" }, "defaultInst": { "portRef": "pnpm_ghrel@0.1.0" } }, - "bciqhlgvuozbxcmjecf7hqedxlg3pgvw5eu5yesdmvo645vet3yhm53q": { + "bciqhiohdon4chnr3oagakks2xii27bgc7eb56pglnr5gelp6p2rpw4i": { "manifest": { "ty": "denoWorker@v1", "name": "asdf_plugin_git", @@ -2366,13 +2015,13 @@ "name": "git_aa" } ], - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/asdf_plugin_git.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/asdf_plugin_git.ts" }, "defaultInst": { "portRef": "asdf_plugin_git@0.1.0" } }, - "bciqacaeb3q3ltrfkzhtckfjlnjdis4bwrbhwt44hq6qpbcro623sraq": { + "bciqpjsvpqqmcyqw73qaxquzhxowdylbjv4c6nqfuipx6ngs4rwimxdq": { "manifest": { "ty": "denoWorker@v1", "name": "cpy_bs_ghrel", @@ -2393,7 +2042,7 @@ "name": "zstd_aa" } ], - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/cpy_bs.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/cpy_bs.ts" }, "defaultInst": { "version": "3.12.2", @@ -2401,7 +2050,7 @@ "releaseTag": "20240224" } }, - "bciqenen4rwswbxtiyvcqphbclntmcpdqmvso2egn4ud4ptrkazouani": { + "bciqexpmyf75pimfmu3czcedptwynipi6pztai4ixm6kdj6wwlymesby": { "manifest": { "ty": "denoWorker@v1", "name": "node_org", @@ -2419,28 +2068,28 @@ "name": "tar_aa" } ], - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/node.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/node.ts" }, "defaultInst": { "version": "20.8.0", "portRef": "node_org@0.1.0" } }, - "bciqfvoetwxha3e26koqnilar26rz4ikr2m4rst73oz7swrw6557kdba": { + "bciqgdwpugwdrwifr6qqi3m6vs4y55edt7xkojyxk5zr5m3joowsgkki": { "tar_aa": "bciqb6ua63xodzwxngnbjq35hfikiwzb3dclbqkc7e6xgjdt5jin4pia", "git_aa": "bciqfl5s36w335ducrb6f6gwb3vuwup7vzqwwg67pq42xtkngsnxqobi", "curl_aa": "bciqcfe7qyxmokpn6pgtaj35r5qg74jkehuu6cvyrtcsnegvwlm64oqy", "unzip_aa": "bciqgkpwxjmo5phw5se4ugyiz4xua3xrd54quzmk7wdwpq3vghglogjy", "zstd_aa": "bciqmcvyepuficjj3mwshsbfecwdmzch5gwxqo557icnq4zujtdllh4a", - "rustup_rustlang": "bciqhnfhxvpxn2lci2lbdtigj6i6wqzpqsmijykq2f7bebrpkyrnr7hq", - "rust_rustup": "bciqmcbhawhqin3go4l3rywevxny7lfudhbpffdekhmg35isg7xehvmq", - "cargo_binstall_ghrel": "bciqiiyn3yvadqrhgjmrk7ieqmrsuzd6vbcuvq63x6vpbepgvghhtjja", - "pnpm_ghrel": "bciqjhhjc4a4vtd7oeyh5ei7zxmztzkxr523h27uh7ps6rlw2r2w4ufi", - "asdf_plugin_git": "bciqhlgvuozbxcmjecf7hqedxlg3pgvw5eu5yesdmvo645vet3yhm53q", - "cpy_bs_ghrel": "bciqacaeb3q3ltrfkzhtckfjlnjdis4bwrbhwt44hq6qpbcro623sraq", - "node_org": "bciqenen4rwswbxtiyvcqphbclntmcpdqmvso2egn4ud4ptrkazouani" + "rustup_rustlang": "bciqbbs5l2j72vleufdba2nj4k4dnp3akxv4w74rhhpnz5buoixxnuqy", + "rust_rustup": "bciqfn3guc53gpo32zezii4g6it32cbmb5xli7e47d2qvurnqinz7edq", + "cargo_binstall_ghrel": "bciqj25jnt3hzhwfm6z2tkpfyxt7cpb2dbwavf75doh5d5qnwwdugljy", + "pnpm_ghrel": "bciqfr2ubzsw3lefwt7mwuzlkct2cbwvo4l44hrrigfzx64uocww5rki", + "asdf_plugin_git": "bciqhiohdon4chnr3oagakks2xii27bgc7eb56pglnr5gelp6p2rpw4i", + "cpy_bs_ghrel": "bciqpjsvpqqmcyqw73qaxquzhxowdylbjv4c6nqfuipx6ngs4rwimxdq", + "node_org": "bciqexpmyf75pimfmu3czcedptwynipi6pztai4ixm6kdj6wwlymesby" }, - "bciqd7mu5umyjjnt7v6lmyg4tlhj4bg6oq36iepo42sog2cxfs7tdpdi": { + "bciqj5xfgcxzfw3tusoy4v53dcinxqxskfwe3lylcjevxl6mbroky5za": { "version": "v24.1", "port": { "ty": "denoWorker@v1", @@ -2452,10 +2101,10 @@ "x86_64-darwin" ], "version": "0.1.0", - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/protoc.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/protoc.ts" } }, - "bciqp5sktkn6etkn3z4kvheorj42bloeldjnv75glh6q4jfju4emqt3y": { + "bciqlubbahrp4pxohyffmn5yj52atjgmn5nxepmkdev6wtmvpbx7kr7y": { "buildDepConfigs": { "asdf_plugin_git": { "pluginRepo": "https://github.com/asdf-community/asdf-cmake", @@ -2500,12 +2149,12 @@ "name": "asdf_plugin_git" } ], - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/asdf.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/asdf.ts" }, "pluginRepo": "https://github.com/asdf-community/asdf-cmake", "installType": "version" }, - "bciqddqzemyb6kw24f2nhhy7hyfo62ytfjllvdnfhurqhb7qqlvo4noa": { + "bciqbx637744bfiyvprs77xdnvdt7uuwmtlntfjpwmkda672gklkbpmi": { "version": "v2.4.0", "port": { "ty": "denoWorker@v1", @@ -2520,11 +2169,11 @@ "name": "tar_aa" } ], - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/mold.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/mold.ts" }, "replaceLd": true }, - "bciql6pq2bc6uxp7z5luxf3nn44bx3vdqo7fsz6sonammtbpwsdmwxcy": { + "bciqdoqocirh7aseu5o5hfqaj3sb3pfd3z3rlvig26xttmcmsoljuz6i": { "port": { "ty": "denoWorker@v1", "name": "act_ghrel", @@ -2537,10 +2186,10 @@ "x86_64-windows" ], "version": "0.1.0", - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/act.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/act.ts" } }, - "bciqllhtecyu5p4isjgutrvnb2xgkrryhxy6ooacn5p6ol2rriku6zwy": { + "bciqjsjvkjm6xvoovs6y3y6me32422zr5wc5njs4kwfmmyf6nt6jzv2i": { "port": { "ty": "denoWorker@v1", "name": "cargobi_cratesio", @@ -2573,12 +2222,12 @@ "name": "rust_rustup" } ], - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/cargobi.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/cargobi.ts" }, "crateName": "whiz", "locked": true }, - "bciqfbafead64bhnmec2sxcnc7v4c4m4p4pgehp5fjihkwqxkzkldmoi": { + "bciqdf7jtq3zzhn676t2dr7fyve47fj7coajtymmye353lrrluskjk7q": { "port": { "ty": "denoWorker@v1", "name": "cargobi_cratesio", @@ -2611,12 +2260,12 @@ "name": "rust_rustup" } ], - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/cargobi.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/cargobi.ts" }, "crateName": "wit-deps-cli", "locked": true }, - "bciqn62icb2lxjafe2jzh53frwkycch2tk22lhk5aqkhsdl7cwjbpmji": { + "bciqeaqeduyhykw7s7gq6ney6ci7deheq3etgdwkvg55mwbzdhz2opra": { "port": { "ty": "denoWorker@v1", "name": "cargobi_cratesio", @@ -2649,12 +2298,12 @@ "name": "rust_rustup" } ], - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/cargobi.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/cargobi.ts" }, "crateName": "git-cliff", "locked": true }, - "bciqjq7sv663snw57fsybwrfefbjibkn6t4psfbd2r3dodboafhr6lmq": { + "bciqdtuhf425g6prb5fyupbcokttmkill6wyqk7bkphx3ueltl5mvu4q": { "version": "3.7.1", "port": { "ty": "denoWorker@v1", @@ -2685,11 +2334,11 @@ "name": "cpy_bs_ghrel" } ], - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/pipi.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/pipi.ts" }, "packageName": "pre-commit" }, - "bciqpqesmcpthtkaswi5pb25yqmdea674er4cz7nl2fsa6lsii6ludwa": { + "bciqmvgsg7h3ohj3m7das4bznahgt6tyq7mamta3n2vorulqvml7mywq": { "version": "v0.13.1", "port": { "ty": "denoWorker@v1", @@ -2703,10 +2352,10 @@ "x86_64-windows" ], "version": "0.1.0", - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/temporal_cli.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/temporal_cli.ts" } }, - "bciqctytzcjsv7qqv7kynxwfniayjlocaigb2aihikhlrgt5lab3p32i": { + "bciqicdqw36v63cbrscwsgtu2htrmwmgtfoxexv4rx5d2y24vytxbuma": { "version": "1.33.0", "port": { "ty": "denoWorker@v1", @@ -2740,12 +2389,12 @@ "name": "rust_rustup" } ], - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/cargobi.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/cargobi.ts" }, "crateName": "cargo-insta", "locked": true }, - "bciqm2woxgqgrhy7aa2mqhrbkce7o7xs65iabadfpqyh3xqre5glgsaa": { + "bciqe33uhsuaesrjk6luzxrbbimwg5ydt6x2lrieelwbr7aft4g2qwsy": { "version": "0.2.5", "port": { "ty": "denoWorker@v1", @@ -2779,12 +2428,12 @@ "name": "rust_rustup" } ], - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/cargobi.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/cargobi.ts" }, "crateName": "cross", "locked": true }, - "bciqlajnywjssd2yeb5a2p4qp7was3au33ekjcvashkvei6zr7mdh2dq": { + "bciqgpwvq6qf34xolzh65zr3v5dpyhahtybei46nnjmy2sitnoixwhsa": { "version": "3.8.18", "port": { "ty": "denoWorker@v1", @@ -2806,11 +2455,11 @@ "name": "zstd_aa" } ], - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/cpy_bs.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/cpy_bs.ts" }, "releaseTag": "20240224" }, - "bciqgi2hzel6ndyvbd36ovvcpdv4ltizav3ibmqnujzdp4zzf6zai3vi": { + "bciqkgncxbauys2qfguplxcz2auxrcyamj4b6htqk2fqvohfm3afd7sa": { "version": "0.4.7", "port": { "ty": "denoWorker@v1", @@ -2841,11 +2490,11 @@ "name": "cpy_bs_ghrel" } ], - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/pipi.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/pipi.ts" }, "packageName": "ruff" }, - "bciqhuc6rcywoh677qmscqjbdgofoyalurlnabho5amqmhdfpilrgc5a": { + "bciqh7qqm2bohoswnwjywael2m3f6xn4n6ceifyw4usrmaahjioldq6a": { "version": "1.7.0", "port": { "ty": "denoWorker@v1", @@ -2876,11 +2525,11 @@ "name": "cpy_bs_ghrel" } ], - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/pipi.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/pipi.ts" }, "packageName": "poetry" }, - "bciqkdrtbzm4whp2wklha6js3632cew6reewqcyqt7ordcwg4nfedghi": { + "bciqezep4ufkgwesldlm5etyfkgdsiickfudx7cosydcz6xtgeorn2hy": { "version": "20.8.0", "port": { "ty": "denoWorker@v1", @@ -2899,10 +2548,10 @@ "name": "tar_aa" } ], - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/node.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/node.ts" } }, - "bciqewnlpjw47a2lbkd2qca6jf6opnreaizhblcg5omt2uglljw2onli": { + "bciqaixkkacuuligsvtjcfdfgjgl65owtyspiiljb3vmutlgymecsiwq": { "version": "v9.4.0", "port": { "ty": "denoWorker@v1", @@ -2916,10 +2565,10 @@ "x86_64-windows" ], "version": "0.1.0", - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/pnpm.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/pnpm.ts" } }, - "bciqg3ul5otgtisml3egxsjfe6kvg2jsvl5zakibnaxgiqe2jzv6amei": { + "bciqlt27ioikxnpkqq37hma7ibn5e5wpzfarbvoh77zwdkarwghtvzxa": { "version": "10.0.1", "port": { "ty": "denoWorker@v1", @@ -2950,11 +2599,11 @@ "name": "node_org" } ], - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/npmi.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/npmi.ts" }, "packageName": "node-gyp" }, - "bciqgjly7ijjp5zh5ytt2jcpptqx5vmbgs7lrvn27pujnze6dtljgt6y": { + "bciqojan3zglnfctnmqyxvnxaha46yrnlhj77j3kw4mxadvauqepqdba": { "version": "0.116.1", "port": { "ty": "denoWorker@v1", @@ -2988,12 +2637,12 @@ "name": "rust_rustup" } ], - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/cargobi.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/cargobi.ts" }, "crateName": "wasm-opt", "locked": true }, - "bciqgnqqxpmenmhf5y7psrovuhwdeqh5x5teil2tkggvpgowac3hgjbq": { + "bciqcnbruy2q6trpvia52n2yis4t27taoz4mxkeguqz5aif7ex6rp26y": { "version": "1.208.1", "port": { "ty": "denoWorker@v1", @@ -3027,12 +2676,12 @@ "name": "rust_rustup" } ], - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/cargobi.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/cargobi.ts" }, "crateName": "wasm-tools", "locked": true }, - "bciqh2qn4zwqv7ixf3vb4jzgy2nfzxzz6va5wu5uzv3vtjaqpgg2tygi": { + "bciqpu7gxs3zm7i4gwp3m3cfdxwz27ixvsykdnbxrl5m5mt3xbb3b4la": { "version": "0.13.4", "port": { "ty": "denoWorker@v1", @@ -3063,11 +2712,11 @@ "name": "cpy_bs_ghrel" } ], - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/pipi.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/pipi.ts" }, "packageName": "componentize-py" }, - "bciqdy7ppzs3sotxid2juepk2s7xg7a4g76amjzfeb5ec6kwnwmfnhaq": { + "bciqjme7csfq43oenkrsakdhaha34hgy6vdwkfffki2ank3kf6mjcguq": { "version": "1.3.0", "port": { "ty": "denoWorker@v1", @@ -3098,11 +2747,11 @@ "name": "node_org" } ], - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/npmi.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/npmi.ts" }, "packageName": "@bytecodealliance/jco" }, - "bciqbrqcdc7q732b2hsphpf7t5rzhvz6h5dfqxfxs345t62asad7zcry": { + "bciqezkigmtx5tweeflmn27yofgwybmgbat7g6jg4xmxamomsdpvenay": { "version": "nightly-2024-05-26", "port": { "ty": "denoWorker@v1", @@ -3133,11 +2782,11 @@ "name": "rustup_rustlang" } ], - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/rust.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/rust.ts" }, "profile": "minimal" }, - "bciqjcqwnurxnykxkfkbd7g755fcauqhf4lv5voo5nn62afj3tybfesi": { + "bciqiknd2vbwhxng2oy5d7qjpor7jq74pulaeijfrywyggv4mw3wngsi": { "version": "0.1.47", "port": { "ty": "denoWorker@v1", @@ -3171,7 +2820,7 @@ "name": "rust_rustup" } ], - "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/cargobi.ts" + "moduleSpecifier": "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/cargobi.ts" }, "crateName": "cargo-udeps", "locked": true diff --git a/.github/workflows/publish-website.yml b/.github/workflows/publish-website.yml index c3d2422904..2c8828352e 100644 --- a/.github/workflows/publish-website.yml +++ b/.github/workflows/publish-website.yml @@ -4,7 +4,7 @@ on: - main env: - GHJK_VERSION: "b702292" + GHJK_VERSION: "v0.2.1" jobs: changes: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 785b086416..9d5adc26a0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ on: - v* env: - GHJK_VERSION: "b702292" + GHJK_VERSION: "v0.2.1" GHJK_ENV: "ci" REGISTRY_IMAGE: ghcr.io/${{ github.repository_owner }}/typegate DOCKER_BUILD_NO_SUMMARY: true diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index daaff73336..b799d79c34 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,7 +16,7 @@ on: - ready_for_review env: - GHJK_VERSION: "b702292" + GHJK_VERSION: "v0.2.1" GHJK_ENV: "ci" RUST_BACKTRACE: "full" DENO_DIR: deno-dir diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7a2afca8a6..b88581176f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -28,7 +28,7 @@ repos: - id: deno-fmt name: Deno format language: system - entry: bash -c 'cd typegate && deno fmt --ignore=native,src/typegraphs,tmp,tests/e2e/nextjs && cd ../dev && deno fmt && cd ../typegraph/deno && deno fmt --ignore=node_modules,dist' + entry: bash -c 'cd typegate && deno fmt --ignore=native,src/typegraphs,tmp,tests/e2e/nextjs && cd ../dev && deno fmt && cd ../typegraph/deno && deno fmt --ignore=node_modules,dist && cd ../../libs/metagen/src && deno fmt' pass_filenames: false types: - ts @@ -36,7 +36,7 @@ repos: - id: deno-lint name: Deno lint language: system - entry: bash -c 'cd typegate && deno lint --rules-exclude=no-explicit-any --ignore=native,tmp,tests/e2e/nextjs && cd ../dev && deno lint' + entry: bash -c 'cd typegate && deno lint --rules-exclude=no-explicit-any --ignore=native,tmp,tests/e2e/nextjs && cd ../dev && deno lint && cd ../libs/metagen/src/ && deno lint' pass_filenames: false types: - ts diff --git a/.vscode/settings.json b/.vscode/settings.json index d4e857f974..6f27b88736 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,17 +4,24 @@ "website/.docusaurus": true, "website/node_modules": true }, - "deno.enablePaths": [ - "typegate", - "dev", - "examples/templates/deno", - "examples/typegraphs", - "typegraph/deno/dev", - "typegraph/deno/sdk", - "ghjk.ts", - ".ghjk" + "deno.disablePaths": [ + "meta-lsp", + "typegraph/node", + "examples/templates/node", + "website" ], - "deno.importMap": "typegate/import_map.json", + // "deno.enablePaths": [ + // "typegate", + // "dev", + // "examples/templates/deno", + // "examples/typegraphs", + // "typegraph/deno/dev", + // "typegraph/deno/sdk", + // "libs/metagen", + // "ghjk.ts", + // ".ghjk" + // ], + // "deno.config": "typegate/deno.jsonc", "deno.unstable": true, "typescript.suggest.completeFunctionCalls": true, "typescript.inlayHints.variableTypes.enabled": true, @@ -62,6 +69,8 @@ "wasmedge" ], "python.languageServer": "Pylance", - "python.analysis.extraPaths": ["typegraph/python"], + "python.analysis.extraPaths": [ + "typegraph/python" + ], "prettier.proseWrap": "never" } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cafddb4fae..5e7ccd74e4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -101,7 +101,7 @@ You can install it using the following instructions. ```bash # install ghjk -GHJK_VERSION="b702292" +GHJK_VERSION="v0.2.1" GHJK_INSTALL_HOOK_SHELLS=bash # add more shells if needed curl -fsSL https://raw.githubusercontent.com/metatypedev/ghjk/$GHJK_VERSION/install.sh | sh bash # re-open your shells to have the hooks register diff --git a/Cargo.lock b/Cargo.lock index 4ec3292b68..9cf0b129bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1397,6 +1397,15 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +[[package]] +name = "client_rs_static" +version = "0.0.1" +dependencies = [ + "reqwest 0.12.4", + "serde 1.0.204", + "serde_json", +] + [[package]] name = "clipboard-win" version = "5.4.0" @@ -9069,6 +9078,7 @@ dependencies = [ "async-compression", "base64 0.22.1", "bytes", + "encoding_rs", "futures-channel", "futures-core", "futures-util", @@ -9095,6 +9105,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "sync_wrapper", + "system-configuration", "tokio", "tokio-native-tls", "tokio-rustls 0.25.0", diff --git a/Cargo.toml b/Cargo.toml index c7008deb59..7bf6e5b1ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "libs/*", "libs/metagen/src/mdk_rust/static", + "libs/metagen/src/client_rs/static", "typegate/engine", "typegate/standalone", "meta-cli", @@ -12,6 +13,7 @@ members = [ exclude = [ "typegate/tests/runtimes/wasm_reflected/rust", "typegate/tests/runtimes/wasm_wire/rust", + "typegate/tests/metagen/typegraphs/sample/rs", "libs/pyrt_wit_wire", ] [workspace.package] diff --git a/dev/Dockerfile b/dev/Dockerfile index c54fe628f1..4f50566063 100644 --- a/dev/Dockerfile +++ b/dev/Dockerfile @@ -52,7 +52,7 @@ RUN set -eux; \ ; \ apt clean autoclean; apt autoremove --yes; rm -rf /var/lib/{apt,dpkg,cache,log}/; -ARG GHJK_VERSION=b702292 +ARG GHJK_VERSION=v0.2.1 RUN GHJK_INSTALL_EXE_DIR=/usr/bin GHJK_INSTALL_HOOK_SHELLS=bash \ deno run -A https://raw.github.com/metatypedev/ghjk/$GHJK_VERSION/install.ts diff --git a/dev/consts.ts b/dev/consts.ts index ccedeb1989..505cf73c42 100644 --- a/dev/consts.ts +++ b/dev/consts.ts @@ -3,7 +3,7 @@ export const METATYPE_VERSION = "0.4.8"; export const PUBLISHED_VERSION = "0.4.7"; -export const GHJK_VERSION = "b702292"; +export const GHJK_VERSION = "v0.2.1"; export const GHJK_ACTION_VERSION = "318209a9d215f70716a4ac89dbeb9653a2deb8bc"; export const RUST_VERSION = "1.79.0"; export const DENO_VERSION = "1.45.2"; diff --git a/dev/cross.Dockerfile b/dev/cross.Dockerfile index 551bdfce83..3718d5174a 100644 --- a/dev/cross.Dockerfile +++ b/dev/cross.Dockerfile @@ -36,7 +36,7 @@ RUN set -eux \ unzip ENV GHJK_SHARE_DIR=/ghjk -ARG GHJK_VERSION=b702292 +ARG GHJK_VERSION=v0.2.1 RUN curl -fsSL https://raw.github.com/metatypedev/ghjk/$GHJK_VERSION/install.sh \ | GHJK_INSTALL_EXE_DIR=/usr/bin GHJK_INSTALL_HOOK_SHELLS=bash sh diff --git a/dev/deps.ts b/dev/deps.ts index d8cd1d50aa..e2cda6b45d 100644 --- a/dev/deps.ts +++ b/dev/deps.ts @@ -1,18 +1,18 @@ // Copyright Metatype OÜ, licensed under the Elastic License 2.0. // SPDX-License-Identifier: Elastic-2.0 -export { file } from "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/mod.ts"; -export * from "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/mod.ts"; -export * as ports from "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/mod.ts"; +export { file } from "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/mod.ts"; +export * from "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/mod.ts"; +export * as ports from "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/mod.ts"; export { std_url, zod, -} from "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/deps/common.ts"; +} from "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/deps/common.ts"; export { copyLock, sedLock, -} from "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/std.ts"; -export { downloadFile } from "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/utils/mod.ts"; +} from "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/std.ts"; +export { downloadFile } from "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/utils/mod.ts"; // export * from "../../ghjk/mod.ts"; // export * as ports from "../../ghjk/ports/mod.ts"; // export * as utils from "../../ghjk/utils/mod.ts"; diff --git a/dev/envs/compose.adminer.yml b/dev/envs/compose.adminer.yml index 5b09ac8e3c..af47aa6ec8 100644 --- a/dev/envs/compose.adminer.yml +++ b/dev/envs/compose.adminer.yml @@ -1,6 +1,6 @@ services: adminer: - image: adminer:latest + image: docker.io/library/adminer:latest restart: unless-stopped ports: - 8080:8080 diff --git a/dev/envs/compose.base.yml b/dev/envs/compose.base.yml index 2d60752d79..9c902066bc 100644 --- a/dev/envs/compose.base.yml +++ b/dev/envs/compose.base.yml @@ -1,6 +1,6 @@ services: envoy: - image: envoyproxy/envoy:v1.26-latest + image: docker.io/envoyproxy/envoy:v1.26-latest restart: unless-stopped ports: - "9901:9901" @@ -11,7 +11,7 @@ services: - "host.docker.internal:host-gateway" redis: - image: bitnami/redis:7.0 + image: docker.io/bitnami/redis:7.0 restart: unless-stopped ports: - "6379:6379" diff --git a/dev/envs/compose.imgproxy.yml b/dev/envs/compose.imgproxy.yml index 0475979313..12d457c343 100644 --- a/dev/envs/compose.imgproxy.yml +++ b/dev/envs/compose.imgproxy.yml @@ -1,6 +1,6 @@ services: imgproxy: - image: darthsim/imgproxy:v3.9 + image: docker.io/darthsim/imgproxy:v3.9 restart: unless-stopped ports: - "9002:8080" diff --git a/dev/envs/compose.prisma.yml b/dev/envs/compose.prisma.yml index d28ce104e8..88a03be85c 100644 --- a/dev/envs/compose.prisma.yml +++ b/dev/envs/compose.prisma.yml @@ -1,6 +1,6 @@ services: postgres: - image: postgres:15 + image: docker.io/library/postgres:15 restart: unless-stopped ports: - "5432:5432" diff --git a/dev/tasks-dev.ts b/dev/tasks-dev.ts index 80b55cccb8..edb441eb58 100644 --- a/dev/tasks-dev.ts +++ b/dev/tasks-dev.ts @@ -3,6 +3,8 @@ import { DenoTaskDefArgs } from "./deps.ts"; +const DOCKER_CMD = Deno.env.get("DOCKER_CMD") ?? "docker"; + const tasks: Record = { dev: { desc: "Execute dev/*.ts scripts.", @@ -51,14 +53,14 @@ const tasks: Record = { } if (on.size > 0) { - await $.raw`docker compose ${ + await $.raw`${DOCKER_CMD} compose ${ [...on].flatMap((file) => [ "-f", file, ]) } up -d --remove-orphans`; } else { - await $.raw`docker compose ${ + await $.raw`${DOCKER_CMD} compose ${ Object.values(files).flatMap((file) => [ "-f", file, @@ -87,7 +89,6 @@ const tasks: Record = { "a4lNi0PbEItlFZbus1oeH/+wyIxi9uH6TpL8AIqIaMBNvp7SESmuUBbfUwC0prxhGhZqHw8vMDYZAGMhSZ4fLw==", TG_ADMIN_PASSWORD: "password", TG_PORT: "7891", - // TMP_DIR: $.path(projectDir).join("tmp").toString(), }, fn: ($) => $`cargo run -p typegate`, }, diff --git a/examples/deno.lock b/examples/deno.lock index 74dc2b0ac4..c5348d4df9 100644 --- a/examples/deno.lock +++ b/examples/deno.lock @@ -3,6 +3,7 @@ "packages": { "specifiers": { "npm:@sentry/node@7.70.0": "npm:@sentry/node@7.70.0", + "npm:@typegraph/sdk@0.4.6": "npm:@typegraph/sdk@0.4.6", "npm:chance@1.1.11": "npm:chance@1.1.11", "npm:graphql@16.8.1": "npm:graphql@16.8.1", "npm:lodash": "npm:lodash@4.17.21", @@ -11,6 +12,17 @@ "npm:validator@13.12.0": "npm:validator@13.12.0" }, "npm": { + "@deno/shim-deno-test@0.4.0": { + "integrity": "sha512-oYWcD7CpERZy/TXMTM9Tgh1HD/POHlbY9WpzmAk+5H8DohcxG415Qws8yLGlim3EaKBT2v3lJv01x4G0BosnaQ==", + "dependencies": {} + }, + "@deno/shim-deno@0.16.1": { + "integrity": "sha512-s9v0kzF5bm/o9TgdwvsraHx6QNllYrXXmKzgOG2lh4LFXnVMr2gpjK/c/ve6EflQn1MqImcWmVD8HAv5ahuuZQ==", + "dependencies": { + "@deno/shim-deno-test": "@deno/shim-deno-test@0.4.0", + "which": "which@2.0.2" + } + }, "@sentry-internal/tracing@7.70.0": { "integrity": "sha512-SpbE6wZhs6QwG2ORWCt8r28o1T949qkWx/KeRTCdK4Ub95PQ3Y3DgnqD8Wz//3q50Wt6EZDEibmz4t067g6PPg==", "dependencies": { @@ -52,6 +64,12 @@ "tslib": "tslib@2.6.3" } }, + "@typegraph/sdk@0.4.6": { + "integrity": "sha512-BuE8y9Kwz3tdO8fAEHXVVdImo0rtS++9X3FKjyXDBdWehkPvcz0z91R2f1w9sB6EFvLPqBdB3vbM01TfUMTYZA==", + "dependencies": { + "@deno/shim-deno": "@deno/shim-deno@0.16.1" + } + }, "agent-base@6.0.2": { "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dependencies": { @@ -83,6 +101,10 @@ "debug": "debug@4.3.5" } }, + "isexe@2.0.0": { + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dependencies": {} + }, "lodash@4.17.21": { "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dependencies": {} @@ -102,6 +124,12 @@ "validator@13.12.0": { "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", "dependencies": {} + }, + "which@2.0.2": { + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "isexe@2.0.0" + } } } }, diff --git a/examples/typegraphs/metagen/rs/mdk.rs b/examples/typegraphs/metagen/rs/mdk.rs index ff54e03c39..22d735e57d 100644 --- a/examples/typegraphs/metagen/rs/mdk.rs +++ b/examples/typegraphs/metagen/rs/mdk.rs @@ -3,7 +3,7 @@ #![cfg_attr(rustfmt, rustfmt_skip)] // gen-static-start -#![allow(unused)] +#![allow(dead_code)] pub mod wit { wit_bindgen::generate!({ @@ -109,7 +109,7 @@ impl Router { } pub fn init(&self, args: InitArgs) -> Result { - static MT_VERSION: &str = "0.4.8-0"; + static MT_VERSION: &str = "0.4.8"; if args.metatype_version != MT_VERSION { return Err(InitError::VersionMismatch(MT_VERSION.into())); } @@ -219,20 +219,18 @@ macro_rules! init_mat { // gen-static-end use types::*; pub mod types { - use super::*; - pub type StringDateTime = String; - pub type StringUri = String; + pub type StringDateTime4 = String; + pub type StringUri5 = String; #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct Idv3 { pub title: String, pub artist: String, #[serde(rename = "releaseTime")] - pub release_time: StringDateTime, + pub release_time: StringDateTime4, #[serde(rename = "mp3Url")] - pub mp3_url: StringUri, + pub mp3_url: StringUri5, } } -use stubs::*; pub mod stubs { use super::*; pub trait RemixTrack: Sized + 'static { diff --git a/ghjk.ts b/ghjk.ts index 4789e7afe4..52b61884ec 100644 --- a/ghjk.ts +++ b/ghjk.ts @@ -1,3 +1,5 @@ +// @ts-nocheck: Deno file + import { METATYPE_VERSION, PUBLISHED_VERSION } from "./dev/consts.ts"; import { file, ports, sedLock, semver, stdDeps } from "./dev/deps.ts"; import installs from "./dev/installs.ts"; diff --git a/libs/common/src/typegraph/mod.rs b/libs/common/src/typegraph/mod.rs index d0b70cf068..51931c5741 100644 --- a/libs/common/src/typegraph/mod.rs +++ b/libs/common/src/typegraph/mod.rs @@ -178,4 +178,15 @@ impl Typegraph { .to_owned(); Ok(path) } + + pub fn root(&self) -> Result<(&TypeNodeBase, &ObjectTypeData)> { + if self.types.is_empty() { + bail!("typegraph is empty: no nodes found"); + } + let root = &self.types[0]; + match root { + TypeNode::Object { base, data } => Ok((base, data)), + _ => bail!("typegraph is invalid: root node is not object"), + } + } } diff --git a/libs/metagen/fixtures/client_rs/Cargo.toml b/libs/metagen/fixtures/client_rs/Cargo.toml new file mode 100644 index 0000000000..c1b563fa7d --- /dev/null +++ b/libs/metagen/fixtures/client_rs/Cargo.toml @@ -0,0 +1,16 @@ +package.name = "client_rs_static" +package.edition = "2021" +package.version = "0.0.1" + +[dependencies] +serde = { version = "1.0.203", features = ["derive"] } +serde_json = "1.0.117" +tokio = { version = "1", features = ["rt-multi-thread"] } +reqwest = { version = "0.12", features = ["blocking","json"] } + +[workspace] +resolver = "2" + +[[bin]] +name = "main" +path = "main.rs" diff --git a/libs/metagen/fixtures/client_rs/main.rs b/libs/metagen/fixtures/client_rs/main.rs new file mode 100644 index 0000000000..3ee213c9cf --- /dev/null +++ b/libs/metagen/fixtures/client_rs/main.rs @@ -0,0 +1,137 @@ +#![deny(clippy::all)] + +// Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0. +// SPDX-License-Identifier: MPL-2.0 + +#[rustfmt::skip] +pub mod client; +use client::types::*; +use client::*; + +fn main() -> Result<(), BoxErr> { + let port = std::env::var("TG_PORT")?; + let api1 = QueryGraph::new(format!("http://localhost:{port}/sample").parse()?); + + let (res2, res3) = { + // blocking reqwest uses tokio under the hood + let gql_sync = api1.graphql_sync(); + let prepared_m = gql_sync.prepare_mutation(|args| { + Ok(( + api1.scalar_args(args.get("post", |val: PostPartial| val)), + api1.composite_no_args().select(all())?, + api1.composite_args(args.get("id", |id: String| Object21Partial { id: Some(id) })) + .select(all())?, + )) + })?; + + let prepared_clone = prepared_m.clone(); + let res2 = prepared_clone.perform([ + ( + "post", + serde_json::json!(PostPartial { + id: Some("94be5420-8c4a-4e67-b4f4-e1b2b54832a2".into()), + slug: Some("".into()), + title: Some("".into()), + }), + ), + ( + "id", + serde_json::json!("94be5420-8c4a-4e67-b4f4-e1b2b54832a2"), + ), + ])?; + let res3 = gql_sync.query(( + api1.get_user().select_aliased(UserSelections { + posts: alias([ + ( + "post1", + select(PostSelections { + id: get(), + slug: get(), + title: get(), + }), + ), + ("post2", select(PostSelections { id: get(), ..all() })), + ]), + ..all() + })?, + api1.get_posts().select(all())?, + api1.scalar_no_args(), + ))?; + (res2, res3) + }; + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build()? + .block_on(async move { + let gql = api1.graphql(); + let prepared_q = gql.prepare_query(|_args| { + Ok(( + api1.get_user().select_aliased(UserSelections { + posts: alias([ + ( + "post1", + select(PostSelections { + id: get(), + slug: get(), + title: get(), + }), + ), + ("post2", select(PostSelections { id: get(), ..all() })), + ]), + ..all() + })?, + api1.get_posts().select(all())?, + api1.scalar_no_args(), + )) + })?; + + let res1 = prepared_q.perform::([]).await?; + let res1a = prepared_q.perform::([]).await?; + + let res4 = gql + .mutation(( + api1.scalar_args(PostPartial { + id: Some("94be5420-8c4a-4e67-b4f4-e1b2b54832a2".into()), + slug: Some("".into()), + title: Some("".into()), + }), + api1.composite_no_args().select(all())?, + api1.composite_args(Object21Partial { + id: Some("94be5420-8c4a-4e67-b4f4-e1b2b54832a2".into()), + }) + .select(all())?, + )) + .await?; + println!( + "{}", + serde_json::to_string_pretty(&serde_json::json!([ + { + "user": res1.0, + "posts": res1.1, + "scalarNoArgs": res1.2, + }, + { + "user": res1a.0, + "posts": res1a.1, + "scalarNoArgs": res1a.2, + }, + { + "scalarArgs": res2.0, + "compositeNoArgs": res2.1, + "compositeArgs": res2.2, + }, + { + "user": res3.0, + "posts": res3.1, + "scalarNoArgs": res3.2, + }, + { + "scalarArgs": res4.0, + "compositeNoArgs": res4.1, + "compositeArgs": res4.2, + } + ]))? + ); + Ok(()) + }) +} diff --git a/libs/metagen/fixtures/client_ts/deno.json b/libs/metagen/fixtures/client_ts/deno.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/libs/metagen/fixtures/client_ts/deno.json @@ -0,0 +1 @@ +{} diff --git a/libs/metagen/fixtures/client_ts/main.ts b/libs/metagen/fixtures/client_ts/main.ts new file mode 100644 index 0000000000..e8abfc47e1 --- /dev/null +++ b/libs/metagen/fixtures/client_ts/main.ts @@ -0,0 +1,85 @@ +// Copyright Metatype OÜ, licensed under the Elastic License 2.0. +// SPDX-License-Identifier: Elastic-2.0 + +import { alias, PreparedArgs, QueryGraph } from "./client.ts"; + +const api1 = new QueryGraph(); + +const gqlClient = api1.graphql( + `http://localhost:${Deno.env.get("TG_PORT")}/sample`, +); + +const preparedQ = gqlClient.prepareQuery(() => ({ + user: api1.getUser({ + _: "selectAll", + posts: alias({ + post1: { id: true, slug: true, title: true }, + post2: { _: "selectAll", id: false }, + }), + }), + posts: api1.getPosts({ _: "selectAll" }), + + scalarNoArgs: api1.scalarNoArgs(), +})); + +const preparedM = gqlClient.prepareMutation(( + args: PreparedArgs<{ + id: string; + slug: string; + title: string; + }>, +) => ({ + scalarArgs: api1.scalarArgs({ + id: args.get("id"), + slug: args.get("slug"), + title: args.get("title"), + }), + compositeNoArgs: api1.compositeNoArgs({ + _: "selectAll", + }), + compositeArgs: api1.compositeArgs({ + id: args.get("id"), + }, { + _: "selectAll", + }), +})); + +const res1 = await preparedQ.perform({}); +const res1a = await preparedQ.perform({}); + +const res2 = await preparedM.perform({ + id: "94be5420-8c4a-4e67-b4f4-e1b2b54832a2", + slug: "s", + title: "t", +}); + +const res3 = await gqlClient.query({ + user: api1.getUser({ + _: "selectAll", + posts: alias({ + post1: { id: true, slug: true, title: true }, + post2: { _: "selectAll", id: false }, + }), + }), + posts: api1.getPosts({ _: "selectAll" }), + + scalarNoArgs: api1.scalarNoArgs(), +}); + +const res4 = await gqlClient.mutation({ + scalarArgs: api1.scalarArgs({ + id: "94be5420-8c4a-4e67-b4f4-e1b2b54832a2", + slug: "", + title: "", + }), + compositeNoArgs: api1.compositeNoArgs({ + _: "selectAll", + }), + compositeArgs: api1.compositeArgs({ + id: "94be5420-8c4a-4e67-b4f4-e1b2b54832a2", + }, { + _: "selectAll", + }), +}); + +console.log(JSON.stringify([res1, res1a, res2, res3, res4])); diff --git a/libs/metagen/tests/mat_rust/lib.rs b/libs/metagen/fixtures/mat_rust/lib.rs similarity index 100% rename from libs/metagen/tests/mat_rust/lib.rs rename to libs/metagen/fixtures/mat_rust/lib.rs diff --git a/libs/metagen/tests/placeholder b/libs/metagen/fixtures/placeholder similarity index 100% rename from libs/metagen/tests/placeholder rename to libs/metagen/fixtures/placeholder diff --git a/libs/metagen/tests/tg.ts b/libs/metagen/fixtures/tg.ts similarity index 100% rename from libs/metagen/tests/tg.ts rename to libs/metagen/fixtures/tg.ts diff --git a/libs/metagen/fixtures/tg2.ts b/libs/metagen/fixtures/tg2.ts new file mode 100644 index 0000000000..88d1f62a98 --- /dev/null +++ b/libs/metagen/fixtures/tg2.ts @@ -0,0 +1,69 @@ +// Copyright Metatype OÜ, licensed under the Elastic License 2.0. +// SPDX-License-Identifier: Elastic-2.0 + +import { fx, Policy, t, typegraph } from "@typegraph/sdk/index.ts"; +import { DenoRuntime } from "@typegraph/sdk/runtimes/deno.ts"; +import { RandomRuntime } from "@typegraph/sdk/runtimes/random.ts"; + +const genUser = () => ({ + id: "69099108-e48b-43c9-ad02-c6514eaad6e3", + email: "yuse@mail.box", +}); + +const _genPosts = () => [ + { slug: "hair", title: "I dyed my hair!" }, + { slug: "hello", title: "Hello World!" }, +]; + +export const tg = await typegraph({ + name: "sample", + builder(g) { + const random = new RandomRuntime({ seed: 0 }); + const deno = new DenoRuntime(); + + const post = t.struct({ + id: t.uuid({ asId: true, config: { auto: true } }), + slug: t.string(), + title: t.string(), + }, { name: "post" }); + + const user = t.struct({ + id: t.uuid({ asId: true, config: { auto: true } }), + email: t.email(), + posts: t.list(g.ref("post")), + }, { name: "user" }); + + g.expose( + { + getUser: random.gen(user), + getPosts: random.gen(post), + + scalarNoArgs: random.gen(t.string()), + scalarArgs: deno.func( + post, + t.string(), + { + code: () => "hello", + effect: fx.update(), + }, + ), + compositeNoArgs: deno.func(t.struct({}), post, { + code: genUser, + effect: fx.update(), + }), + compositeArgs: deno.func( + t.struct({ id: t.string() }), + post, + { + code: genUser, + effect: fx.update(), + }, + ), + }, + Policy.public(), + ); + }, +}).catch((err) => { + console.log(err); + throw err; +}); diff --git a/libs/metagen/src/client_py/mod.rs b/libs/metagen/src/client_py/mod.rs new file mode 100644 index 0000000000..684018c01f --- /dev/null +++ b/libs/metagen/src/client_py/mod.rs @@ -0,0 +1,362 @@ +// Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0. +// SPDX-License-Identifier: MPL-2.0 + +mod node_metas; + +mod selections; +mod types; +mod utils; + +use core::fmt::Write; + +use common::typegraph::EffectType; +use shared::get_gql_type; + +use crate::interlude::*; +use crate::*; + +use crate::shared::client::*; +use crate::shared::types::NameMemo; +use crate::shared::types::TypeRenderer; +use crate::utils::GenDestBuf; +use utils::normalize_type_title; + +#[derive(Serialize, Deserialize, Debug, garde::Validate)] +pub struct ClienPyGenConfig { + #[serde(flatten)] + #[garde(dive)] + pub base: crate::config::MdkGeneratorConfigBase, +} + +impl ClienPyGenConfig { + pub fn from_json(json: serde_json::Value, workspace_path: &Path) -> anyhow::Result { + let mut config: ClienPyGenConfig = serde_json::from_value(json)?; + config.base.path = workspace_path.join(config.base.path); + config.base.typegraph_path = config + .base + .typegraph_path + .as_ref() + .map(|path| workspace_path.join(path)); + Ok(config) + } +} + +pub struct Generator { + config: ClienPyGenConfig, +} + +impl Generator { + pub const INPUT_TG: &'static str = "tg_name"; + pub fn new(config: ClienPyGenConfig) -> Result { + use garde::Validate; + config.validate(&())?; + Ok(Self { config }) + } +} + +impl crate::Plugin for Generator { + fn bill_of_inputs(&self) -> HashMap { + [( + Self::INPUT_TG.to_string(), + if let Some(tg_name) = &self.config.base.typegraph_name { + GeneratorInputOrder::TypegraphFromTypegate { + name: tg_name.clone(), + } + } else if let Some(tg_path) = &self.config.base.typegraph_path { + GeneratorInputOrder::TypegraphFromPath { + path: tg_path.clone(), + name: self.config.base.typegraph_name.clone(), + } + } else { + unreachable!() + }, + )] + .into_iter() + .collect() + } + + fn generate( + &self, + inputs: HashMap, + ) -> anyhow::Result { + let tg = match inputs + .get(Self::INPUT_TG) + .context("missing generator input")? + { + GeneratorInputResolved::TypegraphFromTypegate { raw } => raw, + GeneratorInputResolved::TypegraphFromPath { raw } => raw, + }; + let mut out = HashMap::new(); + out.insert( + self.config.base.path.join("client.py"), + GeneratedFile { + contents: render_client_py(&self.config, tg)?, + overwrite: true, + }, + ); + + Ok(GeneratorOutput(out)) + } +} + +fn render_client_py(_config: &ClienPyGenConfig, tg: &Typegraph) -> anyhow::Result { + let mut client_py = GenDestBuf { + buf: Default::default(), + }; + + writeln!( + &mut client_py, + "# This file was @generated by metagen and is intended" + )?; + writeln!( + &mut client_py, + "# to be generated again on subsequent metagen runs." + )?; + writeln!(&mut client_py)?; + + render_static(&mut client_py)?; + + let dest: &mut GenDestBuf = &mut client_py; + let manifest = get_manifest(tg)?; + + let name_mapper = NameMapper { + nodes: tg.types.iter().cloned().map(Rc::new).collect(), + memo: Default::default(), + }; + let name_mapper = Rc::new(name_mapper); + + let node_metas = render_node_metas(dest, &manifest, name_mapper.clone())?; + let data_types = render_data_types(dest, &manifest, name_mapper.clone())?; + let data_types = Rc::new(data_types); + let selection_names = + render_selection_types(dest, &manifest, data_types.clone(), name_mapper.clone())?; + + write!( + dest, + r#" +class QueryGraph(QueryGraphBase): + def __init__(self): + super().__init__({{"# + )?; + for (&id, ty_name) in name_mapper.memo.borrow().deref() { + let gql_ty = get_gql_type(&tg.types, id, false); + write!( + dest, + r#" + "{ty_name}": "{gql_ty}","# + )?; + } + write!( + dest, + r#" + }}) + "# + )?; + + for fun in manifest.root_fns { + use heck::ToSnekCase; + + let node_name = fun.name; + let method_name = node_name.to_snek_case(); + let out_ty_name = data_types.get(&fun.out_id).unwrap(); + + let args_row = match ( + fun.in_id.map(|id| data_types.get(&id).unwrap()), + fun.select_ty.map(|id| selection_names.get(&id).unwrap()), + ) { + (Some(arg_ty), Some(select_ty)) => { + format!("self, args: typing.Union[{arg_ty}, PlaceholderArgs], select: {select_ty}") + } + // functions that return scalars don't need selections + (Some(arg_ty), None) => format!("self, args: typing.Union[{arg_ty}, PlaceholderArgs]"), + // not all functions have args (empty struct arg) + (None, Some(select_ty)) => format!("self, select: {select_ty}"), + (None, None) => "self".into(), + }; + + let args_selection = match (fun.in_id, fun.select_ty) { + (Some(_), Some(_)) => "(args, select)", + (Some(_), None) => "args", + (None, Some(_)) => "select", + (None, None) => "True", + }; + + let meta_method = node_metas + .get(&fun.id) + .map(|str| &str[..]) + .unwrap_or_else(|| "scalar"); + + let node_type = match fun.effect { + EffectType::Read => "QueryNode", + EffectType::Update | EffectType::Delete | EffectType::Create => "MutationNode", + }; + + write!( + dest, + r#" + def {method_name}({args_row}) -> {node_type}[{out_ty_name}]: + node = selection_to_nodes( + {{"{node_name}": {args_selection}}}, + {{"{node_name}": NodeDescs.{meta_method}}}, + "$q" + )[0] + return {node_type}(node.node_name, node.instance_name, node.args, node.sub_nodes) +"# + )?; + } + + writeln!(&mut client_py)?; + Ok(client_py.buf) +} + +/// Render the common sections like the transports +fn render_static(dest: &mut GenDestBuf) -> core::fmt::Result { + let client_py = include_str!("static/client.py"); + writeln!(dest, "{}", client_py)?; + Ok(()) +} + +/// Render the types that'll actually hold the data, the ones +/// used for serialization +fn render_data_types( + dest: &mut GenDestBuf, + manifest: &RenderManifest, + name_mapper: Rc, +) -> anyhow::Result { + let mut renderer = + TypeRenderer::new(name_mapper.nodes.clone(), Rc::new(types::PyTypeRenderer {})); + for &id in &manifest.arg_types { + _ = renderer.render(id)?; + } + for &id in &manifest.return_types { + _ = renderer.render(id)?; + } + let (types_ts, name_memo) = renderer.finalize(); + writeln!(dest.buf, "{}", types_ts)?; + Ok(name_memo) +} + +/// Render the type used for selecting fields +fn render_selection_types( + dest: &mut GenDestBuf, + manifest: &RenderManifest, + arg_types_memo: Rc, + name_mapper: Rc, +) -> Result { + let mut renderer = TypeRenderer::new( + name_mapper.nodes.clone(), + Rc::new(selections::PyNodeSelectionsRenderer { + arg_ty_names: arg_types_memo, + }), + ); + for &id in &manifest.selections { + _ = renderer.render(id)?; + } + let (buf, memo) = renderer.finalize(); + write!(dest, "{buf}")?; + Ok(memo) +} + +/// Render the `nodeMetas` object used to encode the query +/// graph metadata +fn render_node_metas( + dest: &mut GenDestBuf, + manifest: &RenderManifest, + name_mapper: Rc, +) -> Result { + let mut renderer = TypeRenderer::new( + name_mapper.nodes.clone(), + Rc::new(node_metas::PyNodeMetasRenderer { name_mapper }), + ); + for &id in &manifest.node_metas { + _ = renderer.render(id)?; + } + let (methods, memo) = renderer.finalize(); + write!( + dest, + r#" +class NodeDescs: + @staticmethod + def scalar(): + return NodeMeta() + {methods} +"# + )?; + Ok(memo) +} + +struct NameMapper { + nodes: Vec>, + memo: std::cell::RefCell, +} + +impl NameMapper { + pub fn name_for(&self, id: u32) -> Rc { + self.memo + .borrow_mut() + .entry(id) + .or_insert_with(|| { + Rc::from(normalize_type_title(&self.nodes[id as usize].base().title)) + }) + .clone() + } +} + +#[test] +fn e2e() -> anyhow::Result<()> { + use crate::tests::*; + + let tg_name = "gen-test"; + let config = config::Config { + targets: [( + "default".to_string(), + config::Target( + [GeneratorConfig { + generator_name: "client_py".to_string(), + other: serde_json::to_value(client_py::ClienPyGenConfig { + base: config::MdkGeneratorConfigBase { + typegraph_name: Some(tg_name.into()), + typegraph_path: None, + // NOTE: root will map to the test's tempdir + path: "./".into(), + }, + })?, + }] + .into_iter() + .collect(), + ), + )] + .into_iter() + .collect(), + }; + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .thread_stack_size(16 * 1024 * 1024) + .build()? + .block_on(async { + let tg = test_typegraph_3().await?; + e2e_test(vec![E2eTestCase { + typegraphs: [(tg_name.to_string(), tg)].into_iter().collect(), + target: "default".into(), + config, + build_fn: |args| { + Box::pin(async move { + let status = tokio::process::Command::new("ruff") + .args("check client.py".split(' ').collect::>()) + .current_dir(&args.path) + .kill_on_drop(true) + .spawn()? + .wait() + .await?; + if !status.success() { + anyhow::bail!("error checking generated module"); + } + Ok(()) + }) + }, + target_dir: None, + }]) + .await + })?; + Ok(()) +} diff --git a/libs/metagen/src/client_py/node_metas.rs b/libs/metagen/src/client_py/node_metas.rs new file mode 100644 index 0000000000..2739819010 --- /dev/null +++ b/libs/metagen/src/client_py/node_metas.rs @@ -0,0 +1,171 @@ +// Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0. +// SPDX-License-Identifier: MPL-2.0 + +use std::fmt::Write; + +use common::typegraph::*; + +use super::utils::normalize_type_title; +use crate::{interlude::*, shared::types::*}; + +pub struct PyNodeMetasRenderer { + pub name_mapper: Rc, +} + +impl PyNodeMetasRenderer { + /// `props` is a map of prop_name -> (TypeName, subNodeName) + fn render_for_object( + &self, + dest: &mut impl Write, + ty_name: &str, + props: IndexMap>, + ) -> std::fmt::Result { + write!( + dest, + r#" + @staticmethod + def {ty_name}(): + return NodeMeta( + sub_nodes={{"# + )?; + for (key, node_ref) in props { + write!( + dest, + r#" + "{key}": NodeDescs.{node_ref},"# + )?; + } + write!( + dest, + r#" + }}, + ) +"# + )?; + Ok(()) + } + + fn render_for_func( + &self, + dest: &mut impl Write, + ty_name: &str, + return_node: &str, + argument_fields: Option>>, + ) -> std::fmt::Result { + write!( + dest, + r#" + @staticmethod + def {ty_name}(): + return NodeMeta( + sub_nodes=NodeDescs.{return_node}().sub_nodes,"# + )?; + if let Some(fields) = argument_fields { + write!( + dest, + r#" + arg_types={{"# + )?; + + for (key, ty) in fields { + write!( + dest, + r#" + "{key}": "{ty}","# + )?; + } + + write!( + dest, + r#" + }},"# + )?; + } + write!( + dest, + r#" + ) +"# + )?; + Ok(()) + } +} + +impl RenderType for PyNodeMetasRenderer { + fn render( + &self, + renderer: &mut TypeRenderer, + cursor: &mut VisitCursor, + ) -> anyhow::Result { + use heck::ToPascalCase; + + let name = match cursor.node.clone().deref() { + TypeNode::Any { .. } => unimplemented!("Any type support not implemented"), + TypeNode::Boolean { .. } + | TypeNode::Float { .. } + | TypeNode::Integer { .. } + | TypeNode::String { .. } + | TypeNode::File { .. } => "scalar".into(), + // list and optional node just return the meta of the wrapped type + TypeNode::Optional { + data: OptionalTypeData { item, .. }, + .. + } + | TypeNode::List { + data: ListTypeData { items: item, .. }, + .. + } => renderer.render_subgraph(*item, cursor)?.0.unwrap().to_string(), + TypeNode::Function { data, base } => { + let (return_ty_name, _cyclic) = renderer.render_subgraph(data.output, cursor)?; + let return_ty_name = return_ty_name.unwrap() ; + let props = match renderer.nodes[data.input as usize].deref() { + TypeNode::Object { data, .. } if !data.properties.is_empty() => { + let props = data + .properties + .iter() + // generate property types first + .map(|(name, &dep_id)| { + eyre::Ok((name.clone(), self.name_mapper.name_for(dep_id))) + }) + .collect::, _>>()?; + Some(props) + } + _ => None, + }; + let node_name = &base.title; + let ty_name = normalize_type_title(node_name).to_pascal_case(); + self.render_for_func(renderer, &ty_name, &return_ty_name, props)?; + ty_name + } + TypeNode::Object { data, base } => { + let props = data + .properties + .iter() + // generate property types first + .map(|(name, &dep_id)| { + let (ty_name, _cyclic) = renderer.render_subgraph(dep_id, cursor)?; + let ty_name = ty_name.unwrap(); + eyre::Ok((name.clone(), ty_name)) + }) + .collect::, _>>()?; + let node_name = &base.title; + let ty_name = normalize_type_title(node_name).to_pascal_case(); + self.render_for_object(renderer, &ty_name, props)?; + ty_name + } + TypeNode::Either { + .. + // data: EitherTypeData { one_of: variants }, + // base, + } + | TypeNode::Union { + .. + // data: UnionTypeData { any_of: variants }, + // base, + } => { + todo!("unions are wip") + } + }; + Ok(name) + } +} diff --git a/libs/metagen/src/client_py/selections.rs b/libs/metagen/src/client_py/selections.rs new file mode 100644 index 0000000000..b6397f6c83 --- /dev/null +++ b/libs/metagen/src/client_py/selections.rs @@ -0,0 +1,98 @@ +// Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0. +// SPDX-License-Identifier: MPL-2.0 + +use std::fmt::Write; + +use common::typegraph::*; + +use super::utils::*; +use crate::{interlude::*, shared::client::*, shared::types::*}; + +pub struct PyNodeSelectionsRenderer { + pub arg_ty_names: Rc, +} + +impl PyNodeSelectionsRenderer { + /// `props` is a map of prop_name -> (SelectionType, ArgumentType) + fn render_for_object( + &self, + dest: &mut impl Write, + ty_name: &str, + props: IndexMap, + ) -> std::fmt::Result { + writeln!(dest, r#"{ty_name} = typing.TypedDict("{ty_name}", {{"#)?; + writeln!(dest, r#" "_": SelectionFlags,"#)?; + for (name, select_ty) in props { + use SelectionTy::*; + match select_ty { + Scalar => writeln!(dest, r#" "{name}": ScalarSelectNoArgs,"#)?, + ScalarArgs { arg_ty } => { + writeln!(dest, r#" "{name}": ScalarSelectArgs["{arg_ty}"],"#)? + } + Composite { select_ty } => writeln!( + dest, + r#" "{name}": CompositeSelectNoArgs["{select_ty}"],"# + )?, + CompositeArgs { arg_ty, select_ty } => writeln!( + dest, + r#" "{name}": CompositeSelectArgs["{arg_ty}", "{select_ty}"],"# + )?, + }; + } + writeln!(dest, "}}, total=False)")?; + writeln!(dest)?; + Ok(()) + } +} + +impl RenderType for PyNodeSelectionsRenderer { + fn render(&self, renderer: &mut TypeRenderer, cursor: &mut VisitCursor) -> Result { + use heck::ToPascalCase; + + let name = match cursor.node.clone().deref() { + TypeNode::Boolean { .. } + | TypeNode::Float { .. } + | TypeNode::Integer { .. } + | TypeNode::String { .. } + | TypeNode::File { .. } => unreachable!("scalars don't get to have selections"), + TypeNode::Any { .. } => unimplemented!("Any type support not implemented"), + TypeNode::Optional { data: OptionalTypeData { item, .. }, .. } + | TypeNode::List { data: ListTypeData { items: item, .. }, .. } + | TypeNode::Function { data:FunctionTypeData { output: item, .. }, .. } + => renderer.render_subgraph(*item, cursor)?.0.unwrap().to_string(), + TypeNode::Object { data, base } => { + let props = data + .properties + .iter() + // generate property types first + .map(|(name, &dep_id)| { + eyre::Ok( + ( + normalize_struct_prop_name(name), + selection_for_field(dep_id, &self.arg_ty_names, renderer, cursor)? + ) + ) + }) + .collect::, _>>()?; + let node_name = &base.title; + let ty_name = normalize_type_title(node_name); + let ty_name = format!("{ty_name}Selections").to_pascal_case(); + self.render_for_object(renderer, &ty_name, props)?; + ty_name + } + TypeNode::Either { + .. + // data: EitherTypeData { one_of: variants }, + // base, + } + | TypeNode::Union { + .. + // data: UnionTypeData { any_of: variants }, + // base, + } => { + todo!("unions are wip") + } + }; + Ok(name) + } +} diff --git a/libs/metagen/src/client_py/static/client.py b/libs/metagen/src/client_py/static/client.py new file mode 100644 index 0000000000..63ab95a030 --- /dev/null +++ b/libs/metagen/src/client_py/static/client.py @@ -0,0 +1,513 @@ +import typing +import dataclasses as dc +import json +import urllib.request as request +import urllib.error +import http.client as http_c + + +def selection_to_nodes( + selection: "SelectionErased", + metas: typing.Dict[str, typing.Callable[[], "NodeMeta"]], + parent_path: str, +) -> typing.List["SelectNode[typing.Any]"]: + out = [] + flags = selection.get("_") + if flags is not None and not isinstance(flags, SelectionFlags): + raise Exception( + f"selection field '_' should be of type SelectionFlags but found {type(flags)}" + ) + select_all = True if flags is not None and flags.select_all else False + found_nodes = set(selection.keys()) + for node_name, meta_fn in metas.items(): + found_nodes.discard(node_name) + + node_selection = selection.get(node_name) + if node_selection is False or (node_selection is None and not select_all): + # this node was not selected + continue + + meta = meta_fn() + + # we splat out any aliasing of nodes here + node_instances = ( + [(key, val) for key, val in node_selection.items.items()] + if isinstance(node_selection, Alias) + else [(node_name, node_selection)] + ) + + for instance_name, instance_selection in node_instances: + # print(parent_path, instance_selection, meta.sub_nodes, instance_selection, flags) + if instance_selection is False or ( + instance_selection is None and not select_all + ): + # this instance was not selected + continue + if isinstance(instance_selection, Alias): + raise Exception( + f"nested Alias node discovored at {parent_path}.{instance_name}" + ) + + instance_args: typing.Optional[NodeArgs] = None + if meta.arg_types is not None: + arg = instance_selection + + if isinstance(arg, tuple): + arg = arg[0] + + # arg types are always TypedDicts + if not isinstance(arg, dict): + raise Exception( + f"node at {parent_path}.{instance_name} is a node that " + + "requires arguments " + + f"but detected argument is typeof {type(arg)}" + ) + + # convert arg dict to NodeArgs + expected_args = {key: val for key, val in meta.arg_types.items()} + instance_args = {} + for key, val in arg.items(): + ty_name = expected_args.pop(key) + if ty_name is None: + raise Exception( + f"unexpected argument ${key} at {parent_path}.{instance_name}" + ) + instance_args[key] = NodeArgValue(ty_name, val) + + sub_nodes: typing.Optional[typing.List[SelectNode]] = None + if meta.sub_nodes is not None: + sub_selections = instance_selection + + # if node requires both selection and arg, it must be + # a CompositeSelectArgs which is a tuple selection + if meta.arg_types is not None: + if not isinstance(sub_selections, tuple): + raise Exception( + f"node at {parent_path}.{instance_name} is a composite " + + "that requires an argument object " + + f"but selection is typeof {type(sub_selections)}" + ) + sub_selections = sub_selections[1] + + elif isinstance(sub_selections, tuple): + raise Exception( + f"node at {parent_path}.{instance_name} " + + "is a composite that takes no arguments " + + f"but selection is typeof {type(instance_selection)}", + ) + + # selection types are always TypedDicts as well + if not isinstance(sub_selections, dict): + raise Exception( + f"node at {parent_path}.{instance_name} " + + "is a no argument composite but first element of " + + f"selection is typeof {type(instance_selection)}", + ) + sub_nodes = selection_to_nodes( + typing.cast("SelectionErased", sub_selections), + meta.sub_nodes, + f"{parent_path}.{instance_name}", + ) + + node = SelectNode(node_name, instance_name, instance_args, sub_nodes) + out.append(node) + + found_nodes.discard("_") + if len(found_nodes) > 0: + raise Exception( + f"unexpected nodes found in selection set at {parent_path}: {found_nodes}", + ) + return out + + +# +# --- --- Util types --- --- # +# + +Out = typing.TypeVar("Out", covariant=True) + +T = typing.TypeVar("T") + +ArgT = typing.TypeVar("ArgT", bound=typing.Mapping[str, typing.Any]) +SelectionT = typing.TypeVar("SelectionT") + + +# +# --- --- Graph node types --- --- # +# + + +@dc.dataclass +class SelectNode(typing.Generic[Out]): + node_name: str + instance_name: str + args: typing.Optional["NodeArgs"] + sub_nodes: typing.Optional[typing.List["SelectNode"]] + + +@dc.dataclass +class QueryNode(SelectNode[Out]): + pass + + +@dc.dataclass +class MutationNode(SelectNode[Out]): + pass + + +@dc.dataclass +class NodeMeta: + sub_nodes: typing.Optional[typing.Dict[str, typing.Callable[[], "NodeMeta"]]] = None + arg_types: typing.Optional[typing.Dict[str, str]] = None + + +# +# --- --- Argument types --- --- # +# + + +@dc.dataclass +class NodeArgValue: + type_name: str + value: typing.Any + + +NodeArgs = typing.Dict[str, NodeArgValue] + + +class PlaceholderValue(typing.Generic[T]): + def __init__(self, key: str): + self.key = key + + +PlaceholderArgs = typing.Dict[str, PlaceholderValue] + + +class PreparedArgs: + def get(self, key: str) -> PlaceholderValue: + return PlaceholderValue(key) + + +# +# --- --- Selection types --- --- # +# + + +class Alias(typing.Generic[SelectionT]): + """ + Request multiple instances of a single node under different + aliases. + """ + + def __init__(self, **aliases: SelectionT): + self.items = aliases + + +ScalarSelectNoArgs = typing.Union[bool, Alias[typing.Literal[True]], None] +ScalarSelectArgs = typing.Union[ + ArgT, + PlaceholderArgs, + Alias[typing.Union[ArgT, PlaceholderArgs]], + typing.Literal[False], + None, +] +CompositeSelectNoArgs = typing.Union[ + SelectionT, Alias[SelectionT], typing.Literal[False], None +] +CompositeSelectArgs = typing.Union[ + typing.Tuple[typing.Union[ArgT, PlaceholderArgs], SelectionT], + Alias[typing.Tuple[typing.Union[ArgT, PlaceholderArgs], SelectionT]], + typing.Literal[False], + None, +] + + +# FIXME: ideally this would be a TypedDict +# to allow full dict based queries but +# we need to reliably identify SelectionFlags at runtime +# but TypedDicts don't allow instanceof +@dc.dataclass +class SelectionFlags: + select_all: typing.Union[bool, None] = None + + +class Selection(typing.TypedDict, total=False): + _: SelectionFlags + + +SelectionErased = typing.Mapping[ + str, + typing.Union[ + SelectionFlags, + ScalarSelectNoArgs, + ScalarSelectArgs[typing.Mapping[str, typing.Any]], + CompositeSelectNoArgs["SelectionErased"], + # FIXME: should be possible to make SelectionT here `SelectionErased` recursively + # but something breaks + CompositeSelectArgs[typing.Mapping[str, typing.Any], typing.Any], + ], +] + +# +# --- --- GraphQL types --- --- # +# + + +@dc.dataclass +class GraphQLTransportOptions: + headers: typing.Dict[str, str] + + +@dc.dataclass +class GraphQLRequest: + addr: str + method: str + headers: typing.Dict[str, str] + body: bytes + + +@dc.dataclass +class GraphQLResponse: + req: GraphQLRequest + status: int + headers: typing.Dict[str, str] + body: bytes + + +def convert_query_node_gql( + node: SelectNode, + variables: typing.Dict[str, NodeArgValue], +): + out = ( + f"{node.instance_name}: {node.node_name}" + if node.instance_name != node.node_name + else node.node_name + ) + if node.args is not None: + arg_row = "" + for key, val in node.args.items(): + name = f"in{len(variables)}" + variables[name] = val + arg_row += f"{key}: ${name}, " + if len(arg_row): + out += f"({arg_row[:-2]})" + + if node.sub_nodes is not None: + sub_node_list = "" + for node in node.sub_nodes: + sub_node_list += f"{convert_query_node_gql(node, variables)} " + out += f" {{ {sub_node_list}}}" + return out + + +class GraphQLTransportBase: + def __init__( + self, + addr: str, + opts: GraphQLTransportOptions, + ty_to_gql_ty_map: typing.Dict[str, str], + ): + self.addr = addr + self.opts = opts + self.ty_to_gql_ty_map = ty_to_gql_ty_map + + def build_gql( + self, + query: typing.Mapping[str, SelectNode], + ty: typing.Union[typing.Literal["query"], typing.Literal["mutation"]], + name: str = "", + ): + variables: typing.Dict[str, NodeArgValue] = {} + root_nodes = "" + for key, node in query.items(): + fixed_node = SelectNode(node.node_name, key, node.args, node.sub_nodes) + root_nodes += f" {convert_query_node_gql(fixed_node, variables)}\n" + args_row = "" + for key, val in variables.items(): + args_row += f"${key}: {self.ty_to_gql_ty_map[val.type_name]}, " + + if len(args_row): + args_row = f"({args_row[:-2]})" + + doc = f"{ty} {name}{args_row} {{\n{root_nodes}}}" + return (doc, {key: val.value for key, val in variables.items()}) + + def build_req( + self, + doc: str, + variables: typing.Dict[str, typing.Any], + opts: typing.Optional[GraphQLTransportOptions] = None, + ): + headers = {} + headers.update(self.opts.headers) + if opts: + headers.update(opts.headers) + headers.update( + { + "accept": "application/json", + "content-type": "application/json", + } + ) + data = json.dumps({"query": doc, "variables": variables}).encode("utf-8") + return GraphQLRequest( + addr=self.addr, + method="POST", + headers=headers, + body=data, + ) + + def handle_response(self, res: GraphQLResponse): + if res.status != 200: + raise Exception(f"graphql request failed with status {res.status}", res) + if res.headers.get("content-type") != "application/json": + raise Exception("unexpected content-type in graphql response", res) + parsed = json.loads(res.body) + if parsed.get("errors"): + raise Exception("graphql errors in response", parsed) + return parsed["data"] + + +class GraphQLTransportUrlib(GraphQLTransportBase): + def fetch( + self, + doc: str, + variables: typing.Dict[str, typing.Any], + opts: typing.Optional[GraphQLTransportOptions], + ): + req = self.build_req(doc, variables, opts) + try: + with request.urlopen( + request.Request( + url=req.addr, method=req.method, headers=req.headers, data=req.body + ) + ) as res: + http_res: http_c.HTTPResponse = res + return self.handle_response( + GraphQLResponse( + req, + status=http_res.status, + body=http_res.read(), + headers={key: val for key, val in http_res.headers.items()}, + ) + ) + except request.HTTPError as res: + return self.handle_response( + GraphQLResponse( + req, + status=res.status or 599, + body=res.read(), + headers={key: val for key, val in res.headers.items()}, + ) + ) + except urllib.error.URLError as err: + raise Exception(f"URL error: {err.reason}") + + def query( + self, + inp: typing.Dict[str, QueryNode[Out]], + opts: typing.Optional[GraphQLTransportOptions] = None, + name: str = "", + ) -> typing.Dict[str, Out]: + doc, variables = self.build_gql( + {key: val for key, val in inp.items()}, "query", name + ) + # print(doc,variables) + # return {} + return self.fetch(doc, variables, opts) + + def mutation( + self, + inp: typing.Dict[str, MutationNode[Out]], + opts: typing.Optional[GraphQLTransportOptions] = None, + name: str = "", + ) -> typing.Dict[str, Out]: + doc, variables = self.build_gql( + {key: val for key, val in inp.items()}, "mutation", name + ) + return self.fetch(doc, variables, opts) + + def prepare_query( + self, + fun: typing.Callable[[PreparedArgs], typing.Dict[str, QueryNode[Out]]], + name: str = "", + ) -> "PreparedRequestUrlib[Out]": + return PreparedRequestUrlib(self, fun, "query", name) + + def prepare_mutation( + self, + fun: typing.Callable[[PreparedArgs], typing.Dict[str, MutationNode[Out]]], + name: str = "", + ) -> "PreparedRequestUrlib[Out]": + return PreparedRequestUrlib(self, fun, "mutation", name) + + +class PreparedRequestBase(typing.Generic[Out]): + def __init__( + self, + transport: GraphQLTransportBase, + fun: typing.Callable[[PreparedArgs], typing.Mapping[str, SelectNode[Out]]], + ty: typing.Union[typing.Literal["query"], typing.Literal["mutation"]], + name: str = "", + ): + dry_run_node = fun(PreparedArgs()) + doc, variables = transport.build_gql(dry_run_node, ty, name) + self.doc = doc + self._mapping = variables + self.transport = transport + + def resolve_vars( + self, + args: typing.Mapping[str, typing.Any], + mappings: typing.Dict[str, typing.Any], + ): + resolved: typing.Dict[str, typing.Any] = {} + for key, val in mappings.items(): + if isinstance(val, PlaceholderValue): + resolved[key] = args[val.key] + elif isinstance(val, dict): + self.resolve_vars(args, val) + else: + resolved[key] = val + return resolved + + +class PreparedRequestUrlib(PreparedRequestBase[Out]): + def __init__( + self, + transport: GraphQLTransportUrlib, + fun: typing.Callable[[PreparedArgs], typing.Mapping[str, SelectNode[Out]]], + ty: typing.Union[typing.Literal["query"], typing.Literal["mutation"]], + name: str = "", + ): + super().__init__(transport, fun, ty, name) + self.transport = transport + + def perform( + self, + args: typing.Mapping[str, typing.Any], + opts: typing.Optional[GraphQLTransportOptions] = None, + ) -> typing.Dict[str, Out]: + resolved_vars = self.resolve_vars(args, self._mapping) + return self.transport.fetch(self.doc, resolved_vars, opts) + + +# +# --- --- QueryGraph types --- --- # +# + + +class QueryGraphBase: + def __init__(self, ty_to_gql_ty_map: typing.Dict[str, str]): + self.ty_to_gql_ty_map = ty_to_gql_ty_map + + def graphql_sync( + self, addr: str, opts: typing.Optional[GraphQLTransportOptions] = None + ): + return GraphQLTransportUrlib( + addr, opts or GraphQLTransportOptions({}), self.ty_to_gql_ty_map + ) + + +# +# --- --- Typegraph types --- --- # +# diff --git a/libs/metagen/src/client_py/types.rs b/libs/metagen/src/client_py/types.rs new file mode 100644 index 0000000000..0e5933154a --- /dev/null +++ b/libs/metagen/src/client_py/types.rs @@ -0,0 +1,298 @@ +// Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0. +// SPDX-License-Identifier: MPL-2.0 + +use std::fmt::Write; + +use common::typegraph::*; + +use super::utils::{normalize_struct_prop_name, normalize_type_title}; +use crate::{interlude::*, shared::types::*}; + +pub struct PyTypeRenderer {} +impl PyTypeRenderer { + fn render_alias( + &self, + dest: &mut impl Write, + alias_name: &str, + aliased_ty: &str, + ) -> std::fmt::Result { + writeln!(dest, "{alias_name} = {aliased_ty}")?; + writeln!(dest)?; + Ok(()) + } + + /// `props` is a map of prop_name -> (TypeName, serialization_name) + fn render_object_type( + &self, + dest: &mut impl Write, + ty_name: &str, + props: IndexMap, bool)>, + ) -> std::fmt::Result { + writeln!(dest, r#"{ty_name} = typing.TypedDict("{ty_name}", {{"#)?; + for (name, (ty_name, _optional)) in props.into_iter() { + // FIXME: use NotRequired when bumping to python version + // that supports it + // also, remove the total param below + // if optional { + // writeln!(dest, r#" "{name}": typing.NotRequired[{ty_name}],"#)?; + // } else { + // writeln!(dest, r#" "{name}": {ty_name},"#)?; + // } + writeln!(dest, r#" "{name}": {ty_name},"#)?; + } + writeln!(dest, "}}, total=False)")?; + writeln!(dest)?; + Ok(()) + } + + fn render_union_type( + &self, + dest: &mut impl Write, + ty_name: &str, + variants: Vec>, + ) -> std::fmt::Result { + writeln!(dest, "{ty_name} = typing.Union[")?; + for ty_name in variants.into_iter() { + writeln!(dest, " {ty_name},")?; + } + writeln!(dest, "]")?; + writeln!(dest)?; + writeln!(dest)?; + Ok(()) + } + + fn quote_ty_name( + &self, + id: u32, + (ty_name, cyclic): (RenderedName, Option), + renderer: &mut TypeRenderer, + ) -> Rc { + match ty_name { + RenderedName::Name(name) => { + if cyclic.is_some() && !name.contains('"') { + format!(r#""{name}""#).into() + } else { + name + } + } + RenderedName::Placeholder(_name) => renderer.placeholder_string( + id, + Box::new(move |ty_name| { + if !ty_name.contains('"') { + format!(r#""{ty_name}""#) + } else { + ty_name.into() + } + }), + ), + } + } +} + +impl RenderType for PyTypeRenderer { + fn render( + &self, + renderer: &mut TypeRenderer, + cursor: &mut VisitCursor, + ) -> anyhow::Result { + let body_required = type_body_required(cursor.node.clone()); + let name = match cursor.node.clone().deref() { + TypeNode::Function { .. } => "None".into(), + TypeNode::Boolean { base } if body_required => { + let ty_name = normalize_type_title(&base.title); + self.render_alias(renderer, &ty_name, "bool")?; + ty_name + } + TypeNode::Boolean { .. } => "bool".into(), + TypeNode::Float { base, .. } if body_required => { + let ty_name = normalize_type_title(&base.title); + self.render_alias(renderer, &ty_name, "float")?; + ty_name + } + TypeNode::Float { .. } => "float".into(), + TypeNode::Integer { base, .. } if body_required => { + let ty_name = normalize_type_title(&base.title); + self.render_alias(renderer, &ty_name, "int")?; + ty_name + } + TypeNode::Integer { .. } => "int".into(), + TypeNode::String { + base: + TypeNodeBase { + enumeration: Some(variants), + title, + .. + }, + .. + } if body_required => { + let ty_name = normalize_type_title(title); + // variants are valid strings in JSON (validated by the validator) + self.render_union_type( + renderer, + &ty_name, + variants + .iter() + .map(|val| format!("typing.Literal[{val}]").into()) + .collect(), + )?; + ty_name + } + TypeNode::String { + data: + StringTypeData { + format: Some(format), + pattern: None, + min_length: None, + max_length: None, + }, + base: + TypeNodeBase { + title, + enumeration: None, + .. + }, + } if title.starts_with("string_") => { + let ty_name = normalize_type_title(&format!("string_{format}_{}", cursor.id)); + self.render_alias(renderer, &ty_name, "str")?; + ty_name + } + TypeNode::String { base, .. } if body_required => { + let ty_name = normalize_type_title(&base.title); + self.render_alias(renderer, &ty_name, "str")?; + ty_name + } + TypeNode::String { .. } => "str".into(), + TypeNode::File { base, .. } if body_required => { + let ty_name = normalize_type_title(&base.title); + self.render_alias(renderer, &ty_name, "bytes")?; + ty_name + } + TypeNode::File { .. } => "bytes".into(), + TypeNode::Any { base } if body_required => { + let ty_name = normalize_type_title(&base.title); + self.render_alias(renderer, &ty_name, "typing.Any")?; + ty_name + } + TypeNode::Any { .. } => "typing.Any".into(), + TypeNode::Object { data, base } => { + let props = data + .properties + .iter() + // generate property types first + .map(|(name, &dep_id)| { + let ty_name = self.quote_ty_name( + dep_id, + renderer.render_subgraph(dep_id, cursor)?, + renderer, + ); + let optional = matches!( + renderer.nodes[dep_id as usize].deref(), + TypeNode::Optional { .. } + ); + Ok::<_, anyhow::Error>(( + normalize_struct_prop_name(&name[..]), + (ty_name, optional), + )) + }) + .collect::, _>>()?; + let ty_name = normalize_type_title(&base.title); + self.render_object_type(renderer, &ty_name, props)?; + ty_name + } + TypeNode::Either { + data: EitherTypeData { one_of: variants }, + base, + } + | TypeNode::Union { + data: UnionTypeData { any_of: variants }, + base, + } => { + let variants = variants + .iter() + .map(|&dep_id| { + let ty_name = self.quote_ty_name( + dep_id, + renderer.render_subgraph(dep_id, cursor)?, + renderer, + ); + Ok::<_, anyhow::Error>(ty_name) + }) + .collect::, _>>()?; + let ty_name = normalize_type_title(&base.title); + self.render_union_type(renderer, &ty_name, variants)?; + ty_name + } + TypeNode::Optional { + // NOTE: keep this condition + // in sync with similar one above + base, + data: + OptionalTypeData { + default_value: None, + item, + }, + } if base.title.starts_with("optional_") => { + // TODO: handle cyclic case where entire cycle is aliases + let inner_ty_name = + self.quote_ty_name(*item, renderer.render_subgraph(*item, cursor)?, renderer); + format!("typing.Optional[{inner_ty_name}]") + } + TypeNode::Optional { data, base } => { + // TODO: handle cyclic case where entire cycle is aliases + let inner_ty_name = self.quote_ty_name( + data.item, + renderer.render_subgraph(data.item, cursor)?, + renderer, + ); + let ty_name = normalize_type_title(&base.title); + self.render_alias( + renderer, + &ty_name, + &format!("typing.Union[{inner_ty_name}, None]"), + )?; + ty_name + } + TypeNode::List { + // NOTE: keep this condition + // in sync with similar one above + base, + data: + ListTypeData { + min_items: None, + max_items: None, + unique_items, + items, + }, + } if base.title.starts_with("list_") => { + // TODO: handle cyclic case where entire cycle is aliases + let inner_ty_name = + self.quote_ty_name(*items, renderer.render_subgraph(*items, cursor)?, renderer); + if let Some(true) = unique_items { + format!("typing.Set[{inner_ty_name}]") + } else { + format!("typing.List[{inner_ty_name}]") + } + } + TypeNode::List { data, base } => { + // TODO: handle cyclic case where entire cycle is aliases + let inner_ty_name = self.quote_ty_name( + data.items, + renderer.render_subgraph(data.items, cursor)?, + renderer, + ); + let ty_name = normalize_type_title(&base.title); + if let Some(true) = data.unique_items { + self.render_alias(renderer, &ty_name, &format!("typing.Set[{inner_ty_name}]"))?; + } else { + self.render_alias( + renderer, + &ty_name, + &format!("typing.List[{inner_ty_name}]"), + )?; + }; + ty_name + } + }; + Ok(name) + } +} diff --git a/libs/metagen/src/client_py/utils.rs b/libs/metagen/src/client_py/utils.rs new file mode 100644 index 0000000000..789eb05b94 --- /dev/null +++ b/libs/metagen/src/client_py/utils.rs @@ -0,0 +1,42 @@ +// Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0. +// SPDX-License-Identifier: MPL-2.0 + +use heck::*; +use once_cell::sync::Lazy; + +// sourced from +// keyword library https://docs.python.org/3/library/keyword.html +static _KEYWORDS: Lazy> = Lazy::new(|| { + [ + "False", "None", "True", "and", "as", "assert", "async", "await", "break", "class", + "continue", "def", "del", "elif", "else", "except", "finally", "for", "from", "global", + "if", "import", "in", "is", "lambda", "nonlocal", "not", "or", "pass", "raise", "return", + "try", "while", "with", "yield", + ] + .into_iter() + .collect() +}); + +pub fn normalize_type_title(title: &str) -> String { + static RE: Lazy = + Lazy::new(|| regex::Regex::new(r"^(?\d+)(?.*)").unwrap()); + + // TODO: clean up non valid chars + + // clean out underscores at start/end + let title = title.trim_matches('_'); + // move any numbers at start to end + let title = title.to_pascal_case(); + let title = RE.replace(&title, "$rest$startd"); + title.to_string() +} + +/// This assumes title was already through [normalize_type_title] +pub fn normalize_struct_prop_name(title: &str) -> String { + title.to_snek_case() + // if KEYWORDS.contains(&title[..]) { + // format!("{title}_") + // } else { + // title + // } +} diff --git a/libs/metagen/src/client_rs/mod.rs b/libs/metagen/src/client_rs/mod.rs new file mode 100644 index 0000000000..9735c57407 --- /dev/null +++ b/libs/metagen/src/client_rs/mod.rs @@ -0,0 +1,506 @@ +// Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0. +// SPDX-License-Identifier: MPL-2.0 + +mod node_metas; +mod selections; + +use core::fmt::Write; + +use common::typegraph::EffectType; +use shared::get_gql_type; + +use crate::interlude::*; +use crate::*; + +use crate::mdk_rust::utils; +use crate::shared::client::*; +use crate::shared::types::NameMemo; +use crate::shared::types::TypeRenderer; +use crate::utils::GenDestBuf; +use utils::normalize_type_title; + +#[derive(Serialize, Deserialize, Debug, garde::Validate)] +pub struct ClienRsGenConfig { + #[serde(flatten)] + #[garde(dive)] + pub base: crate::config::MdkGeneratorConfigBase, + #[garde(length(min = 1))] + crate_name: Option, + #[garde(skip)] + pub skip_cargo_toml: Option, + #[garde(skip)] + pub skip_lib_rs: Option, +} + +impl ClienRsGenConfig { + pub fn from_json(json: serde_json::Value, workspace_path: &Path) -> anyhow::Result { + let mut config: ClienRsGenConfig = serde_json::from_value(json)?; + config.base.path = workspace_path.join(config.base.path); + config.base.typegraph_path = config + .base + .typegraph_path + .as_ref() + .map(|path| workspace_path.join(path)); + Ok(config) + } +} + +pub struct Generator { + config: ClienRsGenConfig, +} + +impl Generator { + pub const INPUT_TG: &'static str = "tg_name"; + pub fn new(config: ClienRsGenConfig) -> Result { + use garde::Validate; + config.validate(&())?; + Ok(Self { config }) + } +} + +impl crate::Plugin for Generator { + fn bill_of_inputs(&self) -> HashMap { + [( + Self::INPUT_TG.to_string(), + if let Some(tg_name) = &self.config.base.typegraph_name { + GeneratorInputOrder::TypegraphFromTypegate { + name: tg_name.clone(), + } + } else if let Some(tg_path) = &self.config.base.typegraph_path { + GeneratorInputOrder::TypegraphFromPath { + path: tg_path.clone(), + name: self.config.base.typegraph_name.clone(), + } + } else { + unreachable!() + }, + )] + .into_iter() + .collect() + } + + fn generate( + &self, + inputs: HashMap, + ) -> anyhow::Result { + let tg = match inputs + .get(Self::INPUT_TG) + .context("missing generator input")? + { + GeneratorInputResolved::TypegraphFromTypegate { raw } => raw, + GeneratorInputResolved::TypegraphFromPath { raw } => raw, + }; + let mut out = HashMap::new(); + out.insert( + self.config.base.path.join("client.rs"), + GeneratedFile { + contents: render_client_rs(&self.config, tg)?, + overwrite: true, + }, + ); + let crate_name = self.config.crate_name.clone().unwrap_or_else(|| { + use heck::ToSnekCase; + let tg_name = tg.name().unwrap_or_else(|_| "generated".to_string()); + format!("{}_mdk", tg_name.to_snek_case()) + }); + if !matches!(self.config.skip_cargo_toml, Some(true)) { + out.insert( + self.config.base.path.join("Cargo.toml"), + GeneratedFile { + contents: gen_cargo_toml(Some(&crate_name)), + overwrite: false, + }, + ); + } + if !matches!(self.config.skip_lib_rs, Some(true)) { + out.insert( + self.config.base.path.join("lib.rs"), + GeneratedFile { + contents: gen_lib_rs(), + overwrite: false, + }, + ); + } + + Ok(GeneratorOutput(out)) + } +} + +fn render_client_rs(_config: &ClienRsGenConfig, tg: &Typegraph) -> anyhow::Result { + let mut client_rs = GenDestBuf { + buf: Default::default(), + }; + + writeln!( + &mut client_rs, + "// This file was @generated by metagen and is intended" + )?; + writeln!( + &mut client_rs, + "// to be generated again on subsequent metagen runs." + )?; + writeln!(&mut client_rs)?; + + render_static(&mut client_rs)?; + + let dest: &mut GenDestBuf = &mut client_rs; + let manifest = get_manifest(tg)?; + + let name_mapper = NameMapper { + nodes: tg.types.iter().cloned().map(Rc::new).collect(), + memo: Default::default(), + }; + let name_mapper = Rc::new(name_mapper); + + let (node_metas, named_types) = render_node_metas(dest, &manifest, name_mapper.clone())?; + let data_types = render_data_types(dest, &manifest, name_mapper.clone())?; + let data_types = Rc::new(data_types); + let selection_names = + render_selection_types(dest, &manifest, data_types.clone(), name_mapper.clone())?; + + write!( + dest, + r#" +impl QueryGraph {{ + + pub fn new(addr: Url) -> Self {{ + Self {{ + addr, + ty_to_gql_ty_map: std::sync::Arc::new([ + "# + )?; + for (&id, ty_name) in name_mapper.memo.borrow().deref() { + let gql_ty = get_gql_type(&tg.types, id, false); + write!( + dest, + r#" + ("{ty_name}".into(), "{gql_ty}".into()),"# + )?; + } + for id in named_types { + let ty_name = &tg.types[id as usize].base().title; + let gql_ty = get_gql_type(&tg.types, id, false); + write!( + dest, + r#" + ("{ty_name}".into(), "{gql_ty}".into()),"# + )?; + } + write!( + dest, + r#" + ].into()), + }} + }} + "# + )?; + + for fun in manifest.root_fns { + use heck::ToSnekCase; + + let node_name = fun.name; + let method_name = node_name.to_snek_case(); + let out_ty_name = data_types.get(&fun.out_id).unwrap(); + + let arg_ty = fun.in_id.map(|id| data_types.get(&id).unwrap()); + let select_ty = fun.select_ty.map(|id| selection_names.get(&id).unwrap()); + + let (marker_ty, node_ty) = match fun.effect { + EffectType::Read => ("QueryMarker", "QueryNode"), + EffectType::Update | EffectType::Delete | EffectType::Create => { + ("MutationMarker", "MutationNode") + } + }; + + let meta_method = node_metas + .get(&fun.id) + .map(|str| &str[..]) + .unwrap_or_else(|| "scalar"); + + let args_row = match &arg_ty { + Some(arg_ty) => format!( + " + args: impl Into>" + ), + None => "".into(), + }; + match &select_ty { + Some(select_ty) => { + let arg_value = match &arg_ty { + Some(_) => "args.into().into()", + None => "NodeArgsErased::None", + }; + write!( + dest, + r#" + pub fn {method_name}( + &self,{args_row} + ) -> UnselectedNode<{select_ty}, {select_ty}, {marker_ty}, {out_ty_name}> + {{ + UnselectedNode {{ + root_name: "{node_name}".into(), + root_meta: node_metas::{meta_method}, + args: {arg_value}, + _marker: PhantomData, + }} + }}"# + )?; + } + None => { + let arg_value = match &arg_ty { + Some(_) => "SelectionErased::ScalarArgs(args.into().into())", + None => "SelectionErased::Scalar", + }; + write!( + dest, + r#" + pub fn {method_name}( + &self,{args_row} + ) -> {node_ty}<{out_ty_name}> + {{ + let nodes = selection_to_node_set( + SelectionErasedMap( + [( + "{node_name}".into(), + {arg_value}, + )] + .into(), + ), + &[ + ("{node_name}".into(), node_metas::{meta_method} as NodeMetaFn), + ].into(), + "$q".into(), + ) + .unwrap(); + {node_ty}(nodes.into_iter().next().unwrap(), PhantomData) + }}"# + )?; + } + }; + } + writeln!( + dest, + " +}}" + )?; + + writeln!(&mut client_rs)?; + Ok(client_rs.buf) +} + +/// Render the common sections like the transports +fn render_static(dest: &mut GenDestBuf) -> core::fmt::Result { + let client_rs = include_str!("static/client.rs"); + writeln!(dest, "{}", client_rs)?; + Ok(()) +} + +/// Render the types that'll actually hold the data, the ones +/// used for serialization +fn render_data_types( + dest: &mut GenDestBuf, + manifest: &RenderManifest, + name_mapper: Rc, +) -> anyhow::Result { + let mut renderer = TypeRenderer::new( + name_mapper.nodes.clone(), + Rc::new(mdk_rust::types::RustTypeRenderer { + derive_debug: true, + derive_serde: true, + all_fields_optional: true, + }), + ); + for &id in &manifest.arg_types { + _ = renderer.render(id)?; + } + /* renderer.replace_renderer(Rc::new(mdk_rust::types::RustTypeRenderer { + derive_debug: true, + derive_serde: true, + all_fields_optional: true, + })); */ + // FIXME: it should be possible to have non + // partial arg types but + // - rendering the args and return_tys in separately + // results in duplicate common types like for primitives + // - switching out the renderer halfway is troublsome as we + // can't afford to have any non-partial return_tys but + // we might get some if the args refer to one of them + for &id in &manifest.return_types { + _ = renderer.render(id)?; + } + let (types_rs, name_memo) = renderer.finalize(); + writeln!(dest.buf, "use types::*;")?; + writeln!(dest.buf, "pub mod types {{")?; + for line in types_rs.lines() { + writeln!(dest.buf, " {line}")?; + } + writeln!(dest.buf, "}}")?; + Ok(name_memo) +} + +/// Render the type used for selecting fields +fn render_selection_types( + dest: &mut GenDestBuf, + manifest: &RenderManifest, + arg_types_memo: Rc, + name_mapper: Rc, +) -> Result { + let mut renderer = TypeRenderer::new( + name_mapper.nodes.clone(), + Rc::new(selections::RsNodeSelectionsRenderer { + arg_ty_names: arg_types_memo, + }), + ); + for &id in &manifest.selections { + _ = renderer.render(id)?; + } + let (buf, memo) = renderer.finalize(); + write!(dest, "{buf}")?; + Ok(memo) +} + +/// Render the `nodeMetas` object used to encode the query +/// graph metadata +fn render_node_metas( + dest: &mut GenDestBuf, + manifest: &RenderManifest, + name_mapper: Rc, +) -> Result<(NameMemo, HashSet)> { + let named_types = Rc::new(std::sync::Mutex::new(HashSet::new())); + let mut renderer = TypeRenderer::new( + name_mapper.nodes.clone(), + Rc::new(node_metas::RsNodeMetasRenderer { + name_mapper, + named_types: named_types.clone(), + }), + ); + for &id in &manifest.node_metas { + _ = renderer.render(id)?; + } + let (methods, memo) = renderer.finalize(); + write!( + dest, + r#" +#[allow(non_snake_case)] +mod node_metas {{ + use super::*; + pub fn scalar() -> NodeMeta {{ + NodeMeta {{ + arg_types: None, + sub_nodes: None, + variants: None, + }} + }}"# + )?; + for line in methods.lines() { + writeln!(dest, " {line}")?; + } + write!( + dest, + r#" +}} +"# + )?; + Ok(( + memo, + Rc::try_unwrap(named_types).unwrap().into_inner().unwrap(), + )) +} + +struct NameMapper { + nodes: Vec>, + memo: std::cell::RefCell, +} + +impl NameMapper { + pub fn name_for(&self, id: u32) -> Rc { + self.memo + .borrow_mut() + .entry(id) + .or_insert_with(|| { + Rc::from(normalize_type_title(&self.nodes[id as usize].base().title)) + }) + .clone() + } +} + +pub fn gen_cargo_toml(crate_name: Option<&str>) -> String { + let cargo_toml = include_str!("static/Cargo.toml"); + if let Some(crate_name) = crate_name { + const DEF_CRATE_NAME: &str = "client_rs_static"; + cargo_toml.replace(DEF_CRATE_NAME, crate_name) + } else { + cargo_toml.to_string() + } +} + +pub fn gen_lib_rs() -> String { + r#" +mod client; +pub use client::*; + +"# + .into() +} + +#[test] +fn e2e() -> anyhow::Result<()> { + use crate::tests::*; + + let tg_name = "gen-test"; + let config = config::Config { + targets: [( + "default".to_string(), + config::Target( + [GeneratorConfig { + generator_name: "client_rs".to_string(), + other: serde_json::to_value(client_rs::ClienRsGenConfig { + skip_cargo_toml: None, + skip_lib_rs: Some(true), + crate_name: None, + base: config::MdkGeneratorConfigBase { + typegraph_name: Some(tg_name.into()), + typegraph_path: None, + // NOTE: root will map to the test's tempdir + path: "./".into(), + }, + })?, + }] + .into_iter() + .collect(), + ), + )] + .into_iter() + .collect(), + }; + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .thread_stack_size(16 * 1024 * 1024) + .build()? + .block_on(async { + let tg = test_typegraph_3().await?; + e2e_test(vec![E2eTestCase { + typegraphs: [(tg_name.to_string(), tg)].into_iter().collect(), + target: "default".into(), + config, + build_fn: |args| { + Box::pin(async move { + let status = tokio::process::Command::new("cargo") + .args("clippy".split(' ').collect::>()) + .current_dir(args.path) + .kill_on_drop(true) + .spawn()? + .wait() + .await?; + if !status.success() { + anyhow::bail!("error building generated crate"); + } + Ok(()) + }) + }, + target_dir: Some("./fixtures/client_rs/".into()), + }]) + .await + })?; + Ok(()) +} diff --git a/libs/metagen/src/client_rs/node_metas.rs b/libs/metagen/src/client_rs/node_metas.rs new file mode 100644 index 0000000000..14cea854f9 --- /dev/null +++ b/libs/metagen/src/client_rs/node_metas.rs @@ -0,0 +1,241 @@ +// Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0. +// SPDX-License-Identifier: MPL-2.0 + +use std::fmt::Write; + +use common::typegraph::*; + +use super::utils::normalize_type_title; +use crate::{interlude::*, shared::types::*}; + +pub struct RsNodeMetasRenderer { + pub name_mapper: Rc, + pub named_types: Rc>>, +} + +impl RsNodeMetasRenderer { + /// `props` is a map of prop_name -> (TypeName, subNodeName) + fn render_for_object( + &self, + dest: &mut impl Write, + ty_name: &str, + props: IndexMap>, + ) -> std::fmt::Result { + write!( + dest, + r#" +pub fn {ty_name}() -> NodeMeta {{ + NodeMeta {{ + arg_types: None, + variants: None, + sub_nodes: Some( + ["# + )?; + for (key, node_ref) in props { + write!( + dest, + r#" + ("{key}".into(), {node_ref} as NodeMetaFn),"# + )?; + } + write!( + dest, + r#" + ].into() + ), + }} +}}"# + )?; + Ok(()) + } + + fn render_for_union( + &self, + dest: &mut impl Write, + ty_name: &str, + props: IndexMap>, + ) -> std::fmt::Result { + write!( + dest, + r#" +pub fn {ty_name}() -> NodeMeta {{ + NodeMeta {{ + arg_types: None, + sub_nodes: None, + variants: Some( + ["# + )?; + for (key, node_ref) in props { + write!( + dest, + r#" + ("{key}".into(), {node_ref} as NodeMetaFn),"# + )?; + } + write!( + dest, + r#" + ].into() + ), + }} +}}"# + )?; + Ok(()) + } + + fn render_for_func( + &self, + dest: &mut impl Write, + ty_name: &str, + return_node: &str, + argument_fields: Option>>, + ) -> std::fmt::Result { + write!( + dest, + r#" +pub fn {ty_name}() -> NodeMeta {{ + NodeMeta {{"# + )?; + if let Some(fields) = argument_fields { + write!( + dest, + r#" + arg_types: Some( + ["# + )?; + + for (key, ty) in fields { + write!( + dest, + r#" + ("{key}".into(), "{ty}".into()),"# + )?; + } + + write!( + dest, + r#" + ].into() + ),"# + )?; + } + write!( + dest, + r#" + ..{return_node}() + }} +}}"# + )?; + Ok(()) + } +} + +impl RenderType for RsNodeMetasRenderer { + fn render( + &self, + renderer: &mut TypeRenderer, + cursor: &mut VisitCursor, + ) -> anyhow::Result { + use heck::ToPascalCase; + + let name = match cursor.node.clone().deref() { + TypeNode::Any { .. } => unimplemented!("Any type support not implemented"), + TypeNode::Boolean { .. } + | TypeNode::Float { .. } + | TypeNode::Integer { .. } + | TypeNode::String { .. } + | TypeNode::File { .. } => "scalar".into(), + // list and optional node just return the meta of the wrapped type + TypeNode::Optional { + data: OptionalTypeData { item, .. }, + .. + } + | TypeNode::List { + data: ListTypeData { items: item, .. }, + .. + } => renderer + .render_subgraph(*item, cursor)? + .0 + .unwrap() + .to_string(), + TypeNode::Function { data, base } => { + let (return_ty_name, _cyclic) = renderer.render_subgraph(data.output, cursor)?; + let return_ty_name = return_ty_name.unwrap(); + let props = match renderer.nodes[data.input as usize].deref() { + TypeNode::Object { data, .. } if !data.properties.is_empty() => { + let props = data + .properties + .iter() + // generate property types first + .map(|(name, &dep_id)| { + eyre::Ok((name.clone(), self.name_mapper.name_for(dep_id))) + }) + .collect::, _>>()?; + Some(props) + } + _ => None, + }; + let node_name = &base.title; + let ty_name = normalize_type_title(node_name).to_pascal_case(); + self.render_for_func(renderer, &ty_name, &return_ty_name, props)?; + ty_name + } + TypeNode::Object { data, base } => { + let props = data + .properties + .iter() + // generate property types first + .map(|(name, &dep_id)| { + let (ty_name, _cyclic) = renderer.render_subgraph(dep_id, cursor)?; + let ty_name = ty_name.unwrap(); + eyre::Ok((name.clone(), ty_name)) + }) + .collect::, _>>()?; + let node_name = &base.title; + let ty_name = normalize_type_title(node_name).to_pascal_case(); + self.render_for_object(renderer, &ty_name, props)?; + ty_name + } + TypeNode::Either { + data: EitherTypeData { one_of: variants }, + base, + } + | TypeNode::Union { + data: UnionTypeData { any_of: variants }, + base, + } => { + let mut named_set = vec![]; + let variants = variants + .iter() + .filter_map(|&inner| { + if !renderer.is_composite(inner) { + return None; + } + named_set.push(inner); + let (ty_name, _cyclic) = match renderer.render_subgraph(inner, cursor) { + Ok(val) => val, + Err(err) => return Some(Err(err)), + }; + let ty_name = ty_name.unwrap(); + Some(eyre::Ok(( + renderer.nodes[inner as usize].deref().base().title.clone(), + ty_name, + ))) + }) + .collect::, _>>()?; + if !variants.is_empty() { + { + let mut named_types = self.named_types.lock().unwrap(); + named_types.extend(named_set) + } + let ty_name = normalize_type_title(&base.title); + self.render_for_union(renderer, &ty_name, variants)?; + ty_name + } else { + "scalar".into() + } + } + }; + Ok(name) + } +} diff --git a/libs/metagen/src/client_rs/selections.rs b/libs/metagen/src/client_rs/selections.rs new file mode 100644 index 0000000000..fcaf10112e --- /dev/null +++ b/libs/metagen/src/client_rs/selections.rs @@ -0,0 +1,179 @@ +// Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0. +// SPDX-License-Identifier: MPL-2.0 + +use std::fmt::Write; + +use common::typegraph::*; + +use super::utils::*; +use crate::{interlude::*, shared::client::*, shared::types::*}; + +pub struct RsNodeSelectionsRenderer { + pub arg_ty_names: Rc, +} + +impl RsNodeSelectionsRenderer { + fn render_for_struct( + &self, + dest: &mut impl Write, + ty_name: &str, + props: IndexMap, + ) -> std::fmt::Result { + // derive prop + writeln!(dest, "#[derive(Default, Debug)]")?; + writeln!(dest, "pub struct {ty_name} {{")?; + for (name, select_ty) in &props { + use SelectionTy::*; + match select_ty { + Scalar => writeln!(dest, r#" pub {name}: ScalarSelect,"#)?, + ScalarArgs { arg_ty } => { + writeln!(dest, r#" pub {name}: ScalarSelectArgs<{arg_ty}, ATy>,"#)? + } + Composite { select_ty } => writeln!( + dest, + r#" pub {name}: CompositeSelect<{select_ty}, ATy>,"# + )?, + CompositeArgs { arg_ty, select_ty } => writeln!( + dest, + r#" pub {name}: CompositeSelectArgs<{arg_ty}, {select_ty}, ATy>,"# + )?, + }; + } + writeln!(dest, "}}")?; + write!(dest, "impl_selection_traits!({ty_name}, ")?; + let len = props.len(); + for (idx, (name, _)) in props.iter().enumerate() { + if idx < len - 1 { + write!(dest, "{name}, ")?; + } else { + writeln!(dest, "{name});")?; + } + } + Ok(()) + } + fn render_for_union( + &self, + dest: &mut impl Write, + ty_name: &str, + props: IndexMap, + ) -> std::fmt::Result { + // derive prop + writeln!(dest, "#[derive(Default, Debug)]")?; + writeln!(dest, "pub struct {ty_name} {{")?; + for (name, (_variant_ty, select_ty)) in &props { + use SelectionTy::*; + match select_ty { + Scalar | ScalarArgs { .. } => { + // scalars always get selected if the union node + // gets selected + unreachable!() + } + Composite { select_ty } => writeln!( + dest, + r#" pub {name}: CompositeSelect<{select_ty}, NoAlias>,"# + )?, + CompositeArgs { arg_ty, select_ty } => writeln!( + dest, + r#" pub {name}: CompositeSelectArgs<{arg_ty}, {select_ty}, NoAlias>,"# + )?, + }; + } + writeln!(dest, "}}")?; + write!(dest, "impl_union_selection_traits!({ty_name}")?; + for (name, (variant_ty, _)) in props.iter() { + write!(dest, r#", ("{variant_ty}", {name})"#)?; + } + writeln!(dest, r#");"#)?; + Ok(()) + } +} + +impl RenderType for RsNodeSelectionsRenderer { + fn render( + &self, + renderer: &mut TypeRenderer, + cursor: &mut VisitCursor, + ) -> anyhow::Result { + use heck::ToPascalCase; + + let name = match cursor.node.clone().deref() { + TypeNode::Boolean { .. } + | TypeNode::Float { .. } + | TypeNode::Integer { .. } + | TypeNode::String { .. } + | TypeNode::File { .. } => unreachable!("scalars don't get to have selections"), + TypeNode::Any { .. } => unimplemented!("Any type support not implemented"), + TypeNode::Optional { + data: OptionalTypeData { item, .. }, + .. + } + | TypeNode::List { + data: ListTypeData { items: item, .. }, + .. + } + | TypeNode::Function { + data: FunctionTypeData { output: item, .. }, + .. + } => renderer + .render_subgraph(*item, cursor)? + .0 + .unwrap() + .to_string(), + TypeNode::Object { data, base } => { + let props = data + .properties + .iter() + // generate property types first + .map(|(name, &dep_id)| { + eyre::Ok(( + normalize_struct_prop_name(name), + selection_for_field(dep_id, &self.arg_ty_names, renderer, cursor)?, + )) + }) + .collect::, _>>()?; + let node_name = &base.title; + let ty_name = normalize_type_title(node_name); + let ty_name = format!("{ty_name}Selections").to_pascal_case(); + self.render_for_struct(renderer, &ty_name, props)?; + ty_name + } + TypeNode::Either { + data: EitherTypeData { one_of: variants }, + base, + } + | TypeNode::Union { + data: UnionTypeData { any_of: variants }, + base, + } => { + let variants = variants + .iter() + .filter_map(|&inner| { + if !renderer.is_composite(inner) { + return None; + } + let ty_name = renderer.nodes[inner as usize].deref().base().title.clone(); + let struct_prop_name = + normalize_struct_prop_name(&normalize_type_title(&ty_name[..])); + + let selection = match selection_for_field( + inner, + &self.arg_ty_names, + renderer, + cursor, + ) { + Ok(selection) => selection, + Err(err) => return Some(Err(err)), + }; + + Some(eyre::Ok((struct_prop_name, (ty_name, selection)))) + }) + .collect::, _>>()?; + let ty_name = normalize_type_title(&base.title); + let ty_name = format!("{ty_name}Selections").to_pascal_case(); + self.render_for_union(renderer, &ty_name, variants)?; + ty_name + } + }; + Ok(name) + } +} diff --git a/libs/metagen/src/client_rs/static/Cargo.toml b/libs/metagen/src/client_rs/static/Cargo.toml new file mode 100644 index 0000000000..17c96949c0 --- /dev/null +++ b/libs/metagen/src/client_rs/static/Cargo.toml @@ -0,0 +1,14 @@ +package.name = "client_rs_static" +package.edition = "2021" +package.version = "0.0.1" + +[dependencies] +serde = { version = "1.0.203", features = ["derive"] } +serde_json = "1.0.117" +reqwest = { version = "0.12", features = ["blocking","json"] } + +# The options after here are configured for crates intended to be +# wasm artifacts. Remove them if your usage is different +[lib] +path = "lib.rs" +crate-type = ["cdylib", "rlib"] diff --git a/libs/metagen/src/client_rs/static/client.rs b/libs/metagen/src/client_rs/static/client.rs new file mode 100644 index 0000000000..18ee47ae86 --- /dev/null +++ b/libs/metagen/src/client_rs/static/client.rs @@ -0,0 +1,2126 @@ +// Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0. +// SPDX-License-Identifier: MPL-2.0 + +use std::{collections::HashMap, marker::PhantomData}; + +use reqwest::Url; +use serde::{Deserialize, Serialize}; + +pub type CowStr = std::borrow::Cow<'static, str>; +pub type BoxErr = Box; +pub type JsonObject = serde_json::Map; + +fn to_json_value(val: T) -> serde_json::Value { + serde_json::to_value(val).expect("error serializing value") +} + +/// Build the SelectNodeErased tree from the SelectionErasedMap tree +/// according to the NodeMeta tree. In this function +/// - arguments are associated with their types +/// - aliases get splatted into the node tree +/// - light query validation takes place +fn selection_to_node_set( + selection: SelectionErasedMap, + metas: &HashMap, + parent_path: String, +) -> Result, SelectionError> { + let mut out = vec![]; + let mut selection = selection.0; + let mut found_nodes = selection + .keys() + .cloned() + .collect::>(); + for (node_name, meta_fn) in metas.iter() { + found_nodes.remove(&node_name[..]); + + let Some(node_selection) = selection.remove(&node_name[..]) else { + // this node was not selected + continue; + }; + + let node_instances = match node_selection { + SelectionErased::None => continue, + SelectionErased::Scalar => vec![(node_name.clone(), NodeArgsErased::None, None)], + SelectionErased::ScalarArgs(args) => { + vec![(node_name.clone(), args, None)] + } + SelectionErased::Composite(select) => { + vec![(node_name.clone(), NodeArgsErased::None, Some(select))] + } + SelectionErased::CompositeArgs(args, select) => { + vec![(node_name.clone(), args, Some(select))] + } + SelectionErased::Alias(aliases) => aliases + .into_iter() + .map(|(instance_name, selection)| { + let (args, select) = match selection { + AliasSelection::Scalar => (NodeArgsErased::None, None), + AliasSelection::ScalarArgs(args) => (args, None), + AliasSelection::Composite(select) => (NodeArgsErased::None, Some(select)), + AliasSelection::CompositeArgs(args, select) => (args, Some(select)), + }; + (instance_name, args, select) + }) + .collect(), + }; + + let meta = meta_fn(); + for (instance_name, args, select) in node_instances { + let args = if let Some(arg_types) = &meta.arg_types { + match args { + NodeArgsErased::Inline(args) => { + let instance_args = check_node_args(args, arg_types).map_err(|name| { + SelectionError::UnexpectedArgs { + name, + path: format!("{parent_path}.{instance_name}"), + } + })?; + Some(NodeArgsMerged::Inline(instance_args)) + } + NodeArgsErased::Placeholder(ph) => Some(NodeArgsMerged::Placeholder { + value: ph, + // FIXME: this clone can be improved + arg_types: arg_types.clone(), + }), + NodeArgsErased::None => { + return Err(SelectionError::MissingArgs { + path: format!("{parent_path}.{instance_name}"), + }) + } + } + } else { + None + }; + let sub_nodes = match (&meta.variants, &meta.sub_nodes) { + (Some(_), Some(_)) => unreachable!("union/either types can't have sub_nodes"), + (None, None) => SubNodes::None, + (variants, sub_nodes) => { + let Some(select) = select else { + return Err(SelectionError::MissingSubNodes { + path: format!("{parent_path}.{instance_name}"), + }); + }; + match select { + CompositeSelection::Atomic(select) => { + let Some(sub_nodes) = sub_nodes else { + return Err(SelectionError::UnexpectedUnion { + path: format!("{parent_path}.{instance_name}"), + }); + }; + SubNodes::Atomic(selection_to_node_set( + select, + sub_nodes, + format!("{parent_path}.{instance_name}"), + )?) + } + CompositeSelection::Union(variant_select) => { + let Some(variants) = variants else { + return Err(SelectionError::MissingUnion { + path: format!("{parent_path}.{instance_name}"), + }); + }; + let mut out = HashMap::new(); + for (variant_ty, select) in variant_select { + let Some(variant_meta) = variants.get(&variant_ty[..]) else { + return Err(SelectionError::UnexpectedVariant { + path: format!("{parent_path}.{instance_name}"), + varaint_ty: variant_ty.clone(), + }); + }; + let variant_meta = variant_meta(); + // this union member is a scalar + let Some(sub_nodes) = variant_meta.sub_nodes else { + continue; + }; + let nodes = selection_to_node_set( + select, + &sub_nodes, + format!("{parent_path}.{instance_name}"), + )?; + out.insert(variant_ty, nodes); + } + SubNodes::Union(out) + } + } + } + }; + + out.push(SelectNodeErased { + node_name: node_name.clone(), + instance_name, + args, + sub_nodes, + }) + } + } + Ok(out) +} + +#[derive(Debug)] +pub enum SelectionError { + MissingArgs { path: String }, + MissingSubNodes { path: String }, + MissingUnion { path: String }, + UnexpectedArgs { path: String, name: String }, + UnexpectedUnion { path: String }, + UnexpectedVariant { path: String, varaint_ty: CowStr }, +} + +impl std::fmt::Display for SelectionError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SelectionError::MissingArgs { path } => write!(f, "args are missing at node {path}"), + SelectionError::UnexpectedArgs { path, name } => { + write!(f, "unexpected arg '${name}' at node {path}") + } + SelectionError::MissingSubNodes { path } => { + write!(f, "node at {path} is a composite but no selection found") + } + SelectionError::MissingUnion { path } => write!( + f, + "node at {path} is a union but provided selection is atomic" + ), + SelectionError::UnexpectedUnion { path } => write!( + f, + "node at {path} is an atomic type but union selection provided" + ), + SelectionError::UnexpectedVariant { path, varaint_ty } => { + write!(f, "node at {path} has no variant called '{varaint_ty}'") + } + } + } +} +impl std::error::Error for SelectionError {} + +// +// --- --- Graph node types --- --- // +// + +type NodeMetaFn = fn() -> NodeMeta; + +/// How the [`node_metas`] module encodes the description +/// of the typegraph. +struct NodeMeta { + sub_nodes: Option>, + arg_types: Option>, + variants: Option>, +} + +enum SubNodes { + None, + Atomic(Vec), + Union(HashMap>), +} + +/// The final form of the nodes used in queries. +pub struct SelectNodeErased { + node_name: CowStr, + instance_name: CowStr, + args: Option, + sub_nodes: SubNodes, +} + +/// Wrappers around [`SelectNodeErased`] that only holds query nodes +pub struct QueryNode(SelectNodeErased, PhantomData<(Out,)>); +/// Wrappers around [`SelectNodeErased`] that only holds mutation nodes +pub struct MutationNode(SelectNodeErased, PhantomData<(Out,)>); + +/* /// Trait used to track the `Out` type parameter for [`QueryNode`]/[`MutationNode`] +pub trait ToSelectNode { + type Out; + + fn erased(self) -> SelectNodeErased; +} */ + +/// A variation of [`ToSelectNode`] to only be implemented +/// by aggregates of select nodes like [Vec]s. +pub trait ToSelectDoc { + type Out; + + fn to_select_doc(self) -> Vec; + fn parse_response(data: Vec) -> Result; +} + +/// Marker trait for [`ToSelectDoc`] implementors that only carry query nodes. +pub trait ToQueryDoc {} +/// Marker trait for [`ToSelectDoc`] implementors that only carry mutation nodes. +pub trait ToMutationDoc {} + +/// Struct used to mark query associated types that are generic about effect. +pub struct QueryMarker; +/// Struct used to mark mutationo associated types that are generic about effect. +pub struct MutationMarker; + +/// A node that's yet to have it's subnodes specified. +/// Use [`select`][Self::select] and [`select_aliased`][Self::select_aliased] +/// to finalize it. +/// [`select_aliased`][Self::select_aliased] will allow you to use [`alias`] +/// nodes but the returned object will be a raw [`serde_json::Value`]. +/// This type is generic over effect using the `QTy` parameter. +pub struct UnselectedNode { + root_name: CowStr, + root_meta: NodeMetaFn, + args: NodeArgsErased, + _marker: PhantomData<(SelT, SelAliasedT, QTy, Out)>, +} + +impl UnselectedNode +where + SelT: Into, +{ + fn select_erased(self, select: SelT) -> Result { + let nodes = selection_to_node_set( + SelectionErasedMap( + [( + self.root_name.clone(), + match self.args { + NodeArgsErased::None => SelectionErased::Composite(select.into()), + args => SelectionErased::CompositeArgs(args, select.into()), + }, + )] + .into(), + ), + &[(self.root_name, self.root_meta)].into(), + "$q".into(), + )?; + Ok(nodes.into_iter().next().unwrap()) + } +} + +impl UnselectedNode +where + SelAliased: Into, +{ + fn select_aliased_erased(self, select: SelAliased) -> Result { + let nodes = selection_to_node_set( + SelectionErasedMap( + [( + self.root_name.clone(), + match self.args { + NodeArgsErased::None => SelectionErased::Composite(select.into()), + args => SelectionErased::CompositeArgs(args, select.into()), + }, + )] + .into(), + ), + &[(self.root_name, self.root_meta)].into(), + "$q".into(), + )?; + Ok(nodes.into_iter().next().unwrap()) + } +} + +// NOTE: we'll need a select method implementation for each ATy x QTy pair + +impl UnselectedNode +where + SelT: Into, +{ + pub fn select(self, select: SelT) -> Result, SelectionError> { + Ok(QueryNode(self.select_erased(select)?, PhantomData)) + } +} +impl UnselectedNode +where + SelAliased: Into, +{ + pub fn select_aliased( + self, + select: SelAliased, + ) -> Result, SelectionError> { + Ok(QueryNode(self.select_aliased_erased(select)?, PhantomData)) + } +} +impl UnselectedNode +where + SelT: Into, +{ + pub fn select(self, select: SelT) -> Result, SelectionError> { + Ok(MutationNode(self.select_erased(select)?, PhantomData)) + } +} +impl UnselectedNode +where + SelAliased: Into, +{ + pub fn select_aliased( + self, + select: SelAliased, + ) -> Result, SelectionError> { + Ok(MutationNode( + self.select_aliased_erased(select)?, + PhantomData, + )) + } +} + +// --- --- Impl ToSelectDoc --- --- /// + +impl ToSelectDoc for QueryNode +where + Out: serde::de::DeserializeOwned, +{ + type Out = Out; + + fn to_select_doc(self) -> Vec { + vec![self.0] + } + + fn parse_response(data: Vec) -> Result { + let mut data = data.into_iter(); + serde_json::from_value(data.next().unwrap()) + } +} +impl ToQueryDoc for QueryNode {} +impl ToSelectDoc for MutationNode +where + Out: serde::de::DeserializeOwned, +{ + type Out = Out; + + fn to_select_doc(self) -> Vec { + vec![self.0] + } + + fn parse_response(data: Vec) -> Result { + let mut data = data.into_iter(); + serde_json::from_value(data.next().unwrap()) + } +} +impl ToMutationDoc for MutationNode {} + +#[macro_export] +macro_rules! impl_for_tuple { + ($($idx:tt $ty:tt),+) => { + impl<$($ty,)+> ToSelectDoc for ($(QueryNode<$ty>,)+) + where $($ty: serde::de::DeserializeOwned,)+ + { + type Out = ($($ty,)+); + + fn to_select_doc(self) -> Vec { + vec![ + $(self.$idx.0,)+ + ] + } + fn parse_response(data: Vec) -> Result { + let mut data = data.into_iter(); + let mut next = move |_idx| data.next().unwrap(); + Ok(( + + $(serde_json::from_value(next($idx))?,)+ + )) + } + } + impl<$($ty,)+> ToSelectDoc for ($(MutationNode<$ty>,)+) + where $($ty: serde::de::DeserializeOwned,)+ + { + type Out = ($($ty,)+); + + fn to_select_doc(self) -> Vec { + vec![ + $(self.$idx.0,)+ + ] + } + fn parse_response(data: Vec) -> Result { + let mut data = data.into_iter(); + let mut next = move |_idx| data.next().unwrap(); + Ok(( + + $(serde_json::from_value(next($idx))?,)+ + )) + } + } + + impl<$($ty,)+> ToQueryDoc for ($($ty,)+) + where + $($ty: ToQueryDoc,)+ + {} + + impl<$($ty,)+> ToMutationDoc for ($($ty,)+) + where + $($ty: ToMutationDoc,)+ + {} + }; +} + +impl_for_tuple!(0 N0); +impl_for_tuple!(0 N0, 1 N1); +impl_for_tuple!(0 N0, 1 N1, 2 N2); +impl_for_tuple!(0 N0, 1 N1, 2 N2, 3 N3); +impl_for_tuple!(0 N0, 1 N1, 2 N2, 3 N3, 4 N4); +impl_for_tuple!(0 N0, 1 N1, 2 N2, 3 N3, 4 N4, 5 N5); +impl_for_tuple!(0 N0, 1 N1, 2 N2, 3 N3, 4 N4, 5 N5, 6 N6); +impl_for_tuple!(0 N0, 1 N1, 2 N2, 3 N3, 4 N4, 5 N5, 6 N6, 7 N7); +impl_for_tuple!(0 N0, 1 N1, 2 N2, 3 N3, 4 N4, 5 N5, 6 N6, 7 N7, 8 N8); +impl_for_tuple!(0 N0, 1 N1, 2 N2, 3 N3, 4 N4, 5 N5, 6 N6, 7 N7, 8 N8, 9 N9); +impl_for_tuple!(0 N0, 1 N1, 2 N2, 3 N3, 4 N4, 5 N5, 6 N6, 7 N7, 8 N8, 9 N9, 10 N10); +impl_for_tuple!(0 N0, 1 N1, 2 N2, 3 N3, 4 N4, 5 N5, 6 N6, 7 N7, 8 N8, 9 N9, 10 N10, 11 N11); + +// +// --- -- --- Selection types --- --- // +// + +// This is a newtype for Into trait impl purposes +#[derive(Debug)] +pub struct SelectionErasedMap(HashMap); + +#[derive(Debug)] +pub enum CompositeSelection { + Atomic(SelectionErasedMap), + Union(HashMap), +} + +impl Default for CompositeSelection { + fn default() -> Self { + CompositeSelection::Atomic(SelectionErasedMap(Default::default())) + } +} + +#[derive(Debug)] +enum SelectionErased { + None, + Scalar, + ScalarArgs(NodeArgsErased), + Composite(CompositeSelection), + CompositeArgs(NodeArgsErased, CompositeSelection), + Alias(HashMap), +} + +#[derive(Debug)] +pub enum AliasSelection { + Scalar, + ScalarArgs(NodeArgsErased), + Composite(CompositeSelection), + CompositeArgs(NodeArgsErased, CompositeSelection), +} + +#[derive(Default, Clone, Copy, Debug)] +pub struct HasAlias; +#[derive(Default, Clone, Copy, Debug)] +pub struct NoAlias; + +#[derive(Debug)] +pub struct AliasInfo { + aliases: HashMap, + _phantom: PhantomData<(ArgT, SelT, ATyag)>, +} + +#[derive(Debug)] +pub enum ScalarSelect { + Get, + Skip, + Alias(AliasInfo<(), (), ATy>), +} +#[derive(Debug)] +pub enum ScalarSelectArgs { + Get(NodeArgsErased, PhantomData), + Skip, + Alias(AliasInfo), +} +#[derive(Debug)] +pub enum CompositeSelect { + Get(CompositeSelection, PhantomData), + Skip, + Alias(AliasInfo<(), SelT, ATy>), +} +#[derive(Debug)] +pub enum CompositeSelectArgs { + Get( + NodeArgsErased, + CompositeSelection, + PhantomData<(ArgT, SelT)>, + ), + Skip, + Alias(AliasInfo), +} + +pub struct Get; +pub struct Skip; +pub struct Args(ArgT); +pub struct Select(SelT); +pub struct ArgSelect(ArgT, SelT); +pub struct Alias(AliasInfo); + +/// Shorthand for `Default::default`. All selections generally default +/// to [`skip`]. +pub fn default() -> T { + T::default() +} +/// Include all sub nodes excpet those that require arguments +pub fn all() -> T { + T::all() +} +/// Select the node for inclusion. +pub fn get>() -> T { + T::from(Get) +} +/// Skip this node when queryig. +pub fn skip>() -> T { + T::from(Skip) +} +/// Provide argumentns for a scalar node. +pub fn args>>(args: ArgT) -> T { + T::from(Args(args)) +} +/// Provide selections for a composite node that takes no args. +pub fn select>>(selection: SelT) -> T { + T::from(Select(selection)) +} +/// Provide arguments and selections for a composite node. +pub fn arg_select>>(args: ArgT, selection: SelT) -> T { + T::from(ArgSelect(args, selection)) +} + +/// Query the same node multiple times using aliases. +/// +/// WARNING: make sure your alias names don't clash across sibling +/// nodes. +pub fn alias(info: impl Into>) -> T +where + S: Into, + ASelT: Into, + T: From> + FromAliasSelection, +{ + let info: HashMap<_, _> = info.into(); + T::from(Alias(AliasInfo { + aliases: info + .into_iter() + .map(|(name, sel)| (name.into(), sel.into())) + .collect(), + _phantom: PhantomData, + })) +} + +pub trait Selection { + /// Include all sub nodes excpet those that require arguments + fn all() -> Self; +} + +// --- Impl SelectionType impls --- // + +impl Selection for ScalarSelect { + fn all() -> Self { + Self::Get + } +} +impl Selection for ScalarSelectArgs { + fn all() -> Self { + Self::Skip + } +} +impl Selection for CompositeSelect +where + SelT: Selection + Into, +{ + fn all() -> Self { + let sel = SelT::all(); + Self::Get(sel.into(), PhantomData) + } +} +impl Selection for CompositeSelectArgs +where + SelT: Selection, +{ + fn all() -> Self { + Self::Skip + } +} +// --- Default impls --- // + +impl Default for ScalarSelect { + fn default() -> Self { + Self::Skip + } +} +impl Default for ScalarSelectArgs { + fn default() -> Self { + Self::Skip + } +} +impl Default for CompositeSelect { + fn default() -> Self { + Self::Skip + } +} +impl Default for CompositeSelectArgs { + fn default() -> Self { + Self::Skip + } +} + +// --- From Get/Skip...etc impls --- // + +impl From for ScalarSelect { + fn from(_: Get) -> Self { + Self::Get + } +} + +impl From for ScalarSelect { + fn from(_: Skip) -> Self { + Self::Skip + } +} +impl From for ScalarSelectArgs { + fn from(_: Skip) -> Self { + Self::Skip + } +} +impl From for CompositeSelect { + fn from(_: Skip) -> Self { + Self::Skip + } +} +impl From for CompositeSelectArgs { + fn from(_: Skip) -> Self { + Self::Skip + } +} + +impl From> for ScalarSelectArgs +where + ArgT: Serialize, +{ + fn from(Args(args): Args) -> Self { + Self::Get(NodeArgsErased::Inline(to_json_value(args)), PhantomData) + } +} + +impl From> for CompositeSelect +where + SelT: Into, +{ + fn from(Select(selection): Select) -> Self { + Self::Get(selection.into(), PhantomData) + } +} + +impl From> for CompositeSelectArgs +where + ArgT: Serialize, + SelT: Into, +{ + fn from(ArgSelect(args, selection): ArgSelect) -> Self { + Self::Get( + NodeArgsErased::Inline(to_json_value(args)), + selection.into(), + PhantomData, + ) + } +} + +impl From> for ScalarSelectArgs { + fn from(value: PlaceholderArg) -> Self { + Self::Get(NodeArgsErased::Placeholder(value.value), PhantomData) + } +} +impl From> + for CompositeSelectArgs +where + SelT: Into, +{ + fn from(value: PlaceholderArgSelect) -> Self { + Self::Get( + NodeArgsErased::Placeholder(value.value), + value.selection.into(), + PhantomData, + ) + } +} + +// --- ToAliasSelection impls --- // + +/// This is a marker trait that allows the core selection types +/// like CompositeSelectNoArgs to mark which types can be used +/// as their aliasing nodes. This prevents usage of invalid selections +/// on aliases like [`Skip`]. +pub trait FromAliasSelection {} + +impl FromAliasSelection for ScalarSelect {} +impl FromAliasSelection> for ScalarSelectArgs {} +impl FromAliasSelection> for CompositeSelect {} +impl FromAliasSelection> + for CompositeSelectArgs +{ +} + +// --- From Alias impls --- // + +impl From>> for ScalarSelect { + fn from(Alias(info): Alias<(), ScalarSelect>) -> Self { + Self::Alias(AliasInfo { + aliases: info.aliases, + _phantom: PhantomData, + }) + } +} +impl From> for ScalarSelectArgs { + fn from(Alias(info): Alias) -> Self { + Self::Alias(info) + } +} +impl From> for CompositeSelect { + fn from(Alias(info): Alias<(), SelT>) -> Self { + Self::Alias(info) + } +} +impl From> for CompositeSelectArgs { + fn from(Alias(info): Alias) -> Self { + Self::Alias(info) + } +} + +// --- Into SelectionErased impls --- // + +impl From> for SelectionErased { + fn from(value: AliasInfo) -> SelectionErased { + SelectionErased::Alias(value.aliases) + } +} + +impl From> for SelectionErased { + fn from(value: ScalarSelect) -> SelectionErased { + use ScalarSelect::*; + match value { + Get => SelectionErased::Scalar, + Skip => SelectionErased::None, + Alias(alias) => alias.into(), + } + } +} + +impl From> for SelectionErased { + fn from(value: ScalarSelectArgs) -> SelectionErased { + use ScalarSelectArgs::*; + match value { + Get(arg, _) => SelectionErased::ScalarArgs(arg), + Skip => SelectionErased::None, + Alias(alias) => alias.into(), + } + } +} + +impl From> for SelectionErased { + fn from(value: CompositeSelect) -> SelectionErased { + use CompositeSelect::*; + match value { + Get(selection, _) => SelectionErased::Composite(selection), + Skip => SelectionErased::None, + Alias(alias) => alias.into(), + } + } +} + +impl From> for SelectionErased +where + SelT: Into, +{ + fn from(value: CompositeSelectArgs) -> SelectionErased { + use CompositeSelectArgs::*; + match value { + Get(args, selection, _) => SelectionErased::CompositeArgs(args, selection), + Skip => SelectionErased::None, + Alias(alias) => alias.into(), + } + } +} + +// --- UnionMember impls --- // + +/// The following trait is used for types that implement +/// selections for the composite members of unions. +/// +/// The err return value indicates the case where +/// aliases are used selections on members which is an error +/// +/// This state is currently impossible to arrive at since +/// AliasInfo has no public construction methods with NoAlias +/// set. Union selection types make sure all their immediate +/// member selection use NoAlias to prevent this invalid stat.e +pub trait UnionMember { + fn composite(self) -> Option; +} + +/// Internal marker trait use to make sure we can't have union members +/// selection being another union selection. +trait NotUnionSelection {} + +// NOTE: UnionMembers are all NoAlias +impl UnionMember for ScalarSelect { + fn composite(self) -> Option { + None + } +} + +impl UnionMember for ScalarSelectArgs { + fn composite(self) -> Option { + None + } +} + +impl UnionMember for CompositeSelect +where + SelT: NotUnionSelection, +{ + fn composite(self) -> Option { + use CompositeSelect::*; + match self { + Get(CompositeSelection::Atomic(selection), _) => Some(selection), + Skip => None, + Get(CompositeSelection::Union(_), _) => { + unreachable!("union selection on union member selection. how??") + } + Alias(_) => unreachable!("alias discovored on union/either member. how??"), + } + } +} + +impl UnionMember for CompositeSelectArgs +where + SelT: NotUnionSelection, +{ + fn composite(self) -> Option { + use CompositeSelectArgs::*; + match self { + Get(_args, CompositeSelection::Atomic(selection), _) => Some(selection), + Skip => None, + Get(_args, CompositeSelection::Union(_), _) => { + unreachable!("union selection on union member selection. how??") + } + Alias(_) => unreachable!("alias discovored on union/either member. how??"), + } + } +} + +// --- Into AliasSelection impls --- // + +impl From for AliasSelection { + fn from(_val: Get) -> Self { + AliasSelection::Scalar + } +} +impl From> for AliasSelection +where + ArgT: Serialize, +{ + fn from(val: Args) -> Self { + AliasSelection::ScalarArgs(NodeArgsErased::Inline(to_json_value(val.0))) + } +} +impl From> for AliasSelection +where + SelT: Into, +{ + fn from(val: Select) -> Self { + let map = val.0.into(); + AliasSelection::Composite(map) + } +} + +impl From> for AliasSelection +where + ArgT: Serialize, + SelT: Into, +{ + fn from(val: ArgSelect) -> Self { + let map = val.1.into(); + AliasSelection::CompositeArgs(NodeArgsErased::Inline(to_json_value(val.0)), map) + } +} +impl From> for AliasSelection { + fn from(val: ScalarSelect) -> Self { + use ScalarSelect::*; + match val { + Get => AliasSelection::Scalar, + _ => unreachable!(), + } + } +} +impl From> for AliasSelection { + fn from(val: ScalarSelectArgs) -> Self { + use ScalarSelectArgs::*; + match val { + Get(args, _) => AliasSelection::ScalarArgs(args), + _ => unreachable!(), + } + } +} + +impl From> for AliasSelection +where + SelT: Into, +{ + fn from(val: CompositeSelect) -> Self { + use CompositeSelect::*; + match val { + Get(select, _) => AliasSelection::Composite(select), + _ => unreachable!(), + } + } +} +impl From> for AliasSelection +where + SelT: Into, +{ + fn from(val: CompositeSelectArgs) -> Self { + use CompositeSelectArgs::*; + match val { + Get(args, selection, _) => AliasSelection::CompositeArgs(args, selection), + _ => unreachable!(), + } + } +} + +// TODO: convert to proc_macro +#[macro_export] +macro_rules! impl_selection_traits { + ($ty:ident,$($field:tt),+) => { + impl From<$ty> for CompositeSelection { + fn from(value: $ty) -> CompositeSelection { + CompositeSelection::Atomic(SelectionErasedMap( + [ + $((stringify!($field).into(), value.$field.into()),)+ + ] + .into(), + )) + } + } + + impl Selection for $ty { + fn all() -> Self { + Self { + $($field: all(),)+ + } + } + } + + impl NotUnionSelection for $ty {} + }; +} +#[macro_export] +macro_rules! impl_union_selection_traits { + ($ty:ident,$(($variant_ty:tt, $field:tt)),+) => { + impl From<$ty> for CompositeSelection { + fn from(_value: $ty) -> CompositeSelection { + /*CompositeSelection::Union( + [ + $({ + let selection = + UnionMember::composite(value.$field); + selection.map(|val| ($variant_ty.into(), val)) + },)+ + ] + .into_iter() + .filter_map(|val| val) + .collect(), + )*/ + panic!("unions/either are wip") + } + } + }; +} + +// +// --- --- Argument types --- --- // +// + +pub enum NodeArgs { + Inline(ArgT), + Placeholder(PlaceholderValue), +} + +impl From for NodeArgs { + fn from(value: ArgT) -> Self { + Self::Inline(value) + } +} + +#[derive(Debug)] +pub enum NodeArgsErased { + None, + Inline(serde_json::Value), + Placeholder(PlaceholderValue), +} + +impl From> for NodeArgsErased +where + ArgT: Serialize, +{ + fn from(value: NodeArgs) -> Self { + match value { + NodeArgs::Inline(arg) => Self::Inline(to_json_value(arg)), + NodeArgs::Placeholder(ph) => Self::Placeholder(ph), + } + } +} + +enum NodeArgsMerged { + Inline(HashMap), + Placeholder { + value: PlaceholderValue, + arg_types: HashMap, + }, +} + +/// This checks the input arg json for a node +/// against the arg description from the [`NodeMeta`]. +fn check_node_args( + args: serde_json::Value, + arg_types: &HashMap, +) -> Result, String> { + let args = match args { + serde_json::Value::Object(val) => val, + _ => unreachable!(), + }; + let mut instance_args = HashMap::new(); + for (name, value) in args { + let Some(type_name) = arg_types.get(&name[..]) else { + return Err(name); + }; + instance_args.insert( + name.into(), + NodeArgValue { + type_name: type_name.clone(), + value, + }, + ); + } + Ok(instance_args) +} + +struct NodeArgValue { + type_name: CowStr, + value: serde_json::Value, +} + +pub struct PreparedArgs; + +impl PreparedArgs { + pub fn get(&mut self, key: impl Into, fun: F) -> NodeArgs + where + In: serde::de::DeserializeOwned, + F: Fn(In) -> ArgT + 'static + Send + Sync, + ArgT: Serialize, + { + NodeArgs::Placeholder(PlaceholderValue { + key: key.into(), + fun: Box::new(move |value| { + let value = serde_json::from_value(value)?; + let value = fun(value); + serde_json::to_value(value) + }), + }) + } + pub fn arg(&mut self, key: impl Into, fun: F) -> T + where + T: From>, + In: serde::de::DeserializeOwned, + F: Fn(In) -> ArgT + 'static + Send + Sync, + ArgT: Serialize, + { + T::from(PlaceholderArg { + value: PlaceholderValue { + key: key.into(), + fun: Box::new(move |value| { + let value = serde_json::from_value(value)?; + let value = fun(value); + serde_json::to_value(value) + }), + }, + _phantom: PhantomData, + }) + } + pub fn arg_select( + &mut self, + key: impl Into, + selection: SelT, + fun: F, + ) -> T + where + T: From>, + In: serde::de::DeserializeOwned, + F: Fn(In) -> ArgT + 'static + Send + Sync, + ArgT: Serialize, + { + T::from(PlaceholderArgSelect { + value: PlaceholderValue { + key: key.into(), + fun: Box::new(move |value| { + let value = serde_json::from_value(value)?; + let value = fun(value); + serde_json::to_value(value) + }), + }, + selection, + _phantom: PhantomData, + }) + } +} + +pub struct PlaceholderValue { + key: CowStr, + fun: Box< + dyn Fn(serde_json::Value) -> Result + Send + Sync, + >, +} + +impl std::fmt::Debug for PlaceholderValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PlaceholderValue") + .field("key", &self.key) + .finish_non_exhaustive() + } +} + +pub struct PlaceholderArg { + value: PlaceholderValue, + _phantom: PhantomData, +} +pub struct PlaceholderArgSelect { + value: PlaceholderValue, + selection: SelT, + _phantom: PhantomData, +} + +pub struct PlaceholderArgs(Arg); + +// +// --- --- GraphQL types --- --- // +// + +use graphql::*; +pub mod graphql { + use std::sync::Arc; + + use super::*; + + pub(super) type TyToGqlTyMap = Arc>; + + #[derive(Default, Clone)] + pub struct GraphQlTransportOptions { + headers: reqwest::header::HeaderMap, + timeout: Option, + } + + // PlaceholderValue, fieldName -> gql_var_name + type FoundPlaceholders = Vec<(PlaceholderValue, HashMap)>; + + fn select_node_to_gql( + ty_to_gql_ty_map: &TyToGqlTyMap, + dest: &mut impl std::fmt::Write, + node: SelectNodeErased, + variable_types: &mut HashMap, + variables_object: &mut JsonObject, + placeholders: &mut FoundPlaceholders, + ) -> std::fmt::Result { + if node.instance_name != node.node_name { + write!(dest, "{}: {}", node.instance_name, node.node_name)?; + } else { + write!(dest, "{}", node.node_name)?; + } + if let Some(args) = node.args { + match args { + NodeArgsMerged::Inline(args) => { + if !args.is_empty() { + write!(dest, "(")?; + for (key, val) in args { + let name = format!("in{}", variable_types.len()); + write!(dest, "{key}: ${name}, ")?; + variables_object.insert(name.clone(), val.value); + variable_types.insert(name.into(), val.type_name); + } + write!(dest, ")")?; + } + } + NodeArgsMerged::Placeholder { value, arg_types } => { + if !arg_types.is_empty() { + write!(dest, "(")?; + let mut map = HashMap::new(); + for (key, type_name) in arg_types { + let name = format!("in{}", variable_types.len()); + write!(dest, "{key}: ${name}, ")?; + variable_types.insert(name.clone().into(), type_name); + map.insert(key, name.into()); + } + write!(dest, ")")?; + placeholders.push((value, map)); + } + } + } + } + match node.sub_nodes { + SubNodes::None => {} + SubNodes::Atomic(sub_nodes) => { + write!(dest, "{{ ")?; + for node in sub_nodes { + select_node_to_gql( + ty_to_gql_ty_map, + dest, + node, + variable_types, + variables_object, + placeholders, + )?; + write!(dest, " ")?; + } + write!(dest, " }}")?; + } + SubNodes::Union(variants) => { + write!(dest, "{{ ")?; + for (ty, sub_nodes) in variants { + let gql_ty = ty_to_gql_ty_map + .get(&ty[..]) + .expect("impossible: no GraphQL type equivalent found for variant type"); + let gql_ty = match gql_ty.strip_suffix('!') { + Some(val) => val, + None => &gql_ty[..], + }; + write!(dest, " ... on {gql_ty} {{ ")?; + for node in sub_nodes { + select_node_to_gql( + ty_to_gql_ty_map, + dest, + node, + variable_types, + variables_object, + placeholders, + )?; + write!(dest, " ")?; + } + write!(dest, " }}")?; + } + write!(dest, " }}")?; + } + } + Ok(()) + } + + fn build_gql_doc( + ty_to_gql_ty_map: &TyToGqlTyMap, + nodes: Vec, + ty: &'static str, + name: Option, + ) -> Result<(String, JsonObject, FoundPlaceholders), GraphQLRequestError> { + use std::fmt::Write; + let mut variables_types = HashMap::new(); + let mut variables_values = serde_json::Map::new(); + let mut root_nodes = String::new(); + let mut placeholders = vec![]; + for (idx, node) in nodes.into_iter().enumerate() { + let node = SelectNodeErased { + instance_name: format!("node{idx}").into(), + ..node + }; + write!(&mut root_nodes, " ").expect("error building to string"); + select_node_to_gql( + ty_to_gql_ty_map, + &mut root_nodes, + node, + &mut variables_types, + &mut variables_values, + &mut placeholders, + ) + .expect("error building to string"); + writeln!(&mut root_nodes).expect("error building to string"); + } + let mut args_row = String::new(); + if !variables_types.is_empty() { + write!(&mut args_row, "(").expect("error building to string"); + for (key, ty) in &variables_types { + let gql_ty = ty_to_gql_ty_map.get(&ty[..]).ok_or_else(|| { + GraphQLRequestError::InvalidQuery { + error: Box::from(format!("unknown typegraph type found: {}", ty)), + } + })?; + write!(&mut args_row, "${key}: {gql_ty}, ").expect("error building to string"); + } + write!(&mut args_row, ")").expect("error building to string"); + } + let name = name.unwrap_or_else(|| "".into()); + let doc = format!("{ty} {name}{args_row} {{\n{root_nodes}}}"); + Ok((doc, variables_values, placeholders)) + } + + struct GraphQLRequest { + addr: Url, + method: reqwest::Method, + headers: reqwest::header::HeaderMap, + body: serde_json::Value, + } + + fn build_gql_req( + addr: Url, + doc: &str, + variables: &JsonObject, + opts: &GraphQlTransportOptions, + ) -> GraphQLRequest { + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert( + reqwest::header::ACCEPT, + "application/json".try_into().unwrap(), + ); + headers.insert( + reqwest::header::CONTENT_TYPE, + "application/json".try_into().unwrap(), + ); + headers.extend(opts.headers.clone()); + // println!("{doc}, {variables:#?}"); + let body = serde_json::json!({ + "query": doc, + "variables": variables + }); + GraphQLRequest { + addr, + method: reqwest::Method::POST, + headers, + body, + } + } + + #[derive(Debug)] + pub struct GraphQLResponse { + pub status: reqwest::StatusCode, + pub headers: reqwest::header::HeaderMap, + pub body: JsonObject, + } + + fn handle_response( + response: GraphQLResponse, + nodes_len: usize, + ) -> Result, GraphQLRequestError> { + if !response.status.is_success() { + return Err(GraphQLRequestError::RequestFailed { response }); + } + #[derive(Debug, Deserialize)] + struct Response { + data: Option, + errors: Option>, + } + let body: Response = match serde_json::from_value(serde_json::Value::Object(response.body)) + { + Ok(body) => body, + Err(error) => { + return Err(GraphQLRequestError::BodyError { + error: Box::new(error), + }) + } + }; + if let Some(errors) = body.errors { + return Err(GraphQLRequestError::RequestErrors { + errors, + data: body.data, + }); + } + let Some(mut body) = body.data else { + return Err(GraphQLRequestError::BodyError { + error: Box::from("body response doesn't contain data field"), + }); + }; + (0..nodes_len) + .map(|idx| { + body.remove(&format!("node{idx}")) + .ok_or_else(|| GraphQLRequestError::BodyError { + error: Box::from(format!( + "expecting response under node key 'node{idx}' but none found" + )), + }) + }) + .collect::, _>>() + } + + #[derive(Debug)] + pub enum GraphQLRequestError { + /// GraphQL errors recieived + RequestErrors { + errors: Vec, + data: Option, + }, + /// Http error codes recieived + RequestFailed { + response: GraphQLResponse, + }, + /// Unable to deserialize body + BodyError { + error: BoxErr, + }, + /// Unable to make http request + NetworkError { + error: BoxErr, + }, + InvalidQuery { + error: BoxErr, + }, + } + + impl std::fmt::Display for GraphQLRequestError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + GraphQLRequestError::RequestErrors { errors, .. } => { + write!(f, "graphql errors in response: ")?; + for err in errors { + write!(f, "{}, ", err.message)?; + } + } + GraphQLRequestError::RequestFailed { response } => { + write!(f, "request failed with status {}", response.status)?; + } + GraphQLRequestError::BodyError { error } => { + write!(f, "error reading request body: {error}")?; + } + GraphQLRequestError::NetworkError { error } => { + write!(f, "error making http request: {error}")?; + } + GraphQLRequestError::InvalidQuery { error } => { + write!(f, "error building request: {error}")? + } + } + Ok(()) + } + } + impl std::error::Error for GraphQLRequestError {} + + #[derive(Debug, Deserialize)] + pub struct ErrorLocation { + pub line: u32, + pub column: u32, + } + #[derive(Debug, Deserialize)] + pub struct GraphqlError { + pub message: String, + pub locations: Option>, + pub path: Option>, + } + + #[derive(Debug)] + pub enum PathSegment { + Field(String), + Index(u64), + } + + impl<'de> serde::de::Deserialize<'de> for PathSegment { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + use serde_json::Value; + let val = Value::deserialize(deserializer)?; + match val { + Value::Number(n) => Ok(PathSegment::Index(n.as_u64().unwrap())), + Value::String(s) => Ok(PathSegment::Field(s)), + _ => panic!("invalid path segment type"), + } + } + } + + #[derive(Clone)] + pub struct GraphQlTransportReqwestSync { + addr: Url, + ty_to_gql_ty_map: TyToGqlTyMap, + client: reqwest::blocking::Client, + } + + #[derive(Clone)] + pub struct GraphQlTransportReqwest { + addr: Url, + ty_to_gql_ty_map: TyToGqlTyMap, + client: reqwest::Client, + } + + impl GraphQlTransportReqwestSync { + pub fn new(addr: Url, ty_to_gql_ty_map: TyToGqlTyMap) -> Self { + Self { + addr, + ty_to_gql_ty_map, + client: reqwest::blocking::Client::new(), + } + } + + fn fetch( + &self, + nodes: Vec, + opts: &GraphQlTransportOptions, + ty: &'static str, + ) -> Result, GraphQLRequestError> { + let nodes_len = nodes.len(); + let (doc, variables, placeholders) = + build_gql_doc(&self.ty_to_gql_ty_map, nodes, ty, None)?; + if !placeholders.is_empty() { + panic!("placeholders found in non-prepared query") + } + let req = build_gql_req(self.addr.clone(), &doc, &variables, opts); + let req = self + .client + .request(req.method, req.addr) + .headers(req.headers) + .json(&req.body); + let req = if let Some(timeout) = opts.timeout { + req.timeout(timeout) + } else { + req + }; + match req.send() { + Ok(res) => { + let status = res.status(); + let headers = res.headers().clone(); + match res.json::() { + Ok(body) => handle_response( + GraphQLResponse { + status, + headers, + body, + }, + nodes_len, + ), + Err(error) => Err(GraphQLRequestError::BodyError { + error: Box::new(error), + }), + } + } + Err(error) => Err(GraphQLRequestError::NetworkError { + error: Box::new(error), + }), + } + } + + pub fn query( + &self, + nodes: Doc, + ) -> Result { + self.query_with_opts(nodes, &Default::default()) + } + + pub fn query_with_opts( + &self, + nodes: Doc, + opts: &GraphQlTransportOptions, + ) -> Result { + let resp = self.fetch(nodes.to_select_doc(), opts, "query")?; + let resp = Doc::parse_response(resp).map_err(|err| GraphQLRequestError::BodyError { + error: Box::from(format!( + "error deserializing response into output type: {err}" + )), + })?; + Ok(resp) + } + + pub fn mutation( + &self, + nodes: Doc, + ) -> Result { + self.mutation_with_opts(nodes, &Default::default()) + } + + pub fn mutation_with_opts( + &self, + nodes: Doc, + opts: &GraphQlTransportOptions, + ) -> Result { + let resp = self.fetch(nodes.to_select_doc(), opts, "mutation")?; + let resp = Doc::parse_response(resp).map_err(|err| GraphQLRequestError::BodyError { + error: Box::from(format!( + "error deserializing response into output type: {err}" + )), + })?; + Ok(resp) + } + pub fn prepare_query( + &self, + fun: impl FnOnce(&mut PreparedArgs) -> Result, + ) -> Result, PrepareRequestError> { + self.prepare_query_with_opts(fun, Default::default()) + } + + pub fn prepare_query_with_opts( + &self, + fun: impl FnOnce(&mut PreparedArgs) -> Result, + opts: GraphQlTransportOptions, + ) -> Result, PrepareRequestError> { + PreparedRequestReqwestSync::new( + fun, + self.addr.clone(), + opts, + "query", + &self.ty_to_gql_ty_map, + ) + } + + pub fn prepare_mutation( + &self, + fun: impl FnOnce(&mut PreparedArgs) -> Result, + ) -> Result, PrepareRequestError> { + self.prepare_mutation_with_opts(fun, Default::default()) + } + + pub fn prepare_mutation_with_opts( + &self, + fun: impl FnOnce(&mut PreparedArgs) -> Result, + opts: GraphQlTransportOptions, + ) -> Result, PrepareRequestError> { + PreparedRequestReqwestSync::new( + fun, + self.addr.clone(), + opts, + "mutation", + &self.ty_to_gql_ty_map, + ) + } + } + + impl GraphQlTransportReqwest { + pub fn new(addr: Url, ty_to_gql_ty_map: TyToGqlTyMap) -> Self { + Self { + addr, + ty_to_gql_ty_map, + client: reqwest::Client::new(), + } + } + + async fn fetch( + &self, + nodes: Vec, + opts: &GraphQlTransportOptions, + ty: &'static str, + ) -> Result, GraphQLRequestError> { + let nodes_len = nodes.len(); + let (doc, variables, placeholders) = + build_gql_doc(&self.ty_to_gql_ty_map, nodes, ty, None)?; + if !placeholders.is_empty() { + panic!("placeholders found in non-prepared query") + } + let req = build_gql_req(self.addr.clone(), &doc, &variables, opts); + let req = self + .client + .request(req.method, req.addr) + .headers(req.headers) + .json(&req.body); + let req = if let Some(timeout) = opts.timeout { + req.timeout(timeout) + } else { + req + }; + match req.send().await { + Ok(res) => { + let status = res.status(); + let headers = res.headers().clone(); + match res.json::().await { + Ok(body) => handle_response( + GraphQLResponse { + status, + headers, + body, + }, + nodes_len, + ), + Err(error) => Err(GraphQLRequestError::BodyError { + error: Box::new(error), + }), + } + } + Err(error) => Err(GraphQLRequestError::NetworkError { + error: Box::new(error), + }), + } + } + + pub async fn query( + &self, + nodes: Doc, + ) -> Result { + self.query_with_opts(nodes, &Default::default()).await + } + + pub async fn query_with_opts( + &self, + nodes: Doc, + opts: &GraphQlTransportOptions, + ) -> Result { + let resp = self.fetch(nodes.to_select_doc(), opts, "query").await?; + let resp = Doc::parse_response(resp).map_err(|err| GraphQLRequestError::BodyError { + error: Box::from(format!( + "error deserializing response into output type: {err}" + )), + })?; + Ok(resp) + } + + pub async fn mutation( + &self, + nodes: Doc, + ) -> Result { + self.mutation_with_opts(nodes, &Default::default()).await + } + + pub async fn mutation_with_opts( + &self, + nodes: Doc, + opts: &GraphQlTransportOptions, + ) -> Result { + let resp = self.fetch(nodes.to_select_doc(), opts, "mutation").await?; + let resp = Doc::parse_response(resp).map_err(|err| GraphQLRequestError::BodyError { + error: Box::from(format!( + "error deserializing response into output type: {err}" + )), + })?; + Ok(resp) + } + pub fn prepare_query( + &self, + fun: impl FnOnce(&mut PreparedArgs) -> Result, + ) -> Result, PrepareRequestError> { + self.prepare_query_with_opts(fun, Default::default()) + } + + pub fn prepare_query_with_opts( + &self, + fun: impl FnOnce(&mut PreparedArgs) -> Result, + opts: GraphQlTransportOptions, + ) -> Result, PrepareRequestError> { + PreparedRequestReqwest::new( + fun, + self.addr.clone(), + opts, + "query", + &self.ty_to_gql_ty_map, + ) + } + + pub fn prepare_mutation( + &self, + fun: impl FnOnce(&mut PreparedArgs) -> Result, + ) -> Result, PrepareRequestError> { + self.prepare_mutation_with_opts(fun, Default::default()) + } + + pub fn prepare_mutation_with_opts( + &self, + fun: impl FnOnce(&mut PreparedArgs) -> Result, + opts: GraphQlTransportOptions, + ) -> Result, PrepareRequestError> { + PreparedRequestReqwest::new( + fun, + self.addr.clone(), + opts, + "mutation", + &self.ty_to_gql_ty_map, + ) + } + } + + fn resolve_prepared_variables( + placeholders: &FoundPlaceholders, + mut inline_variables: JsonObject, + mut args: HashMap, + ) -> Result { + for (ph, key_map) in placeholders { + let Some(value) = args.remove(&ph.key) else { + return Err(PrepareRequestError::PlaceholderError(Box::from(format!( + "no value found for placeholder expected under key '{}'", + ph.key + )))); + }; + let value = (ph.fun)(value).map_err(|err| { + PrepareRequestError::PlaceholderError(Box::from(format!( + "error applying placeholder closure for value under key '{}': {err}", + ph.key + ))) + })?; + let serde_json::Value::Object(mut value) = value else { + unreachable!("placeholder closures must return structs"); + }; + for (key, var_key) in key_map { + inline_variables.insert( + var_key.clone().into(), + value.remove(&key[..]).unwrap_or(serde_json::Value::Null), + ); + } + } + Ok(inline_variables) + } + + pub struct PreparedRequestReqwest { + addr: Url, + client: reqwest::Client, + nodes_len: usize, + doc: String, + variables: JsonObject, + opts: GraphQlTransportOptions, + placeholders: Arc, + _phantom: PhantomData, + } + + pub struct PreparedRequestReqwestSync { + addr: Url, + client: reqwest::blocking::Client, + nodes_len: usize, + doc: String, + variables: JsonObject, + opts: GraphQlTransportOptions, + placeholders: Arc, + _phantom: PhantomData, + } + + impl PreparedRequestReqwestSync { + fn new( + fun: impl FnOnce(&mut PreparedArgs) -> Result, + addr: Url, + opts: GraphQlTransportOptions, + ty: &'static str, + ty_to_gql_ty_map: &TyToGqlTyMap, + ) -> Result { + let nodes = fun(&mut PreparedArgs).map_err(PrepareRequestError::FunctionError)?; + let nodes = nodes.to_select_doc(); + let nodes_len = nodes.len(); + let (doc, variables, placeholders) = build_gql_doc(ty_to_gql_ty_map, nodes, ty, None) + .map_err(PrepareRequestError::BuildError)?; + Ok(Self { + doc, + variables, + nodes_len, + addr, + client: reqwest::blocking::Client::new(), + opts, + placeholders: Arc::new(placeholders), + _phantom: PhantomData, + }) + } + + pub fn perform( + &self, + args: impl Into>, + ) -> Result + where + K: Into, + V: serde::Serialize, + { + let args: HashMap = args.into(); + let args = args + .into_iter() + .map(|(key, val)| (key.into(), to_json_value(val))) + .collect(); + let variables = + resolve_prepared_variables(&self.placeholders, self.variables.clone(), args)?; + let req = build_gql_req(self.addr.clone(), &self.doc, &variables, &self.opts); + let req = self + .client + .request(req.method, req.addr) + .headers(req.headers) + .json(&req.body); + let req = if let Some(timeout) = self.opts.timeout { + req.timeout(timeout) + } else { + req + }; + let res = match req.send() { + Ok(res) => { + let status = res.status(); + let headers = res.headers().clone(); + match res.json::() { + Ok(body) => handle_response( + GraphQLResponse { + status, + headers, + body, + }, + self.nodes_len, + ) + .map_err(PrepareRequestError::RequestError)?, + Err(error) => { + return Err(PrepareRequestError::RequestError( + GraphQLRequestError::BodyError { + error: Box::new(error), + }, + )) + } + } + } + Err(error) => { + return Err(PrepareRequestError::RequestError( + GraphQLRequestError::NetworkError { + error: Box::new(error), + }, + )) + } + }; + Doc::parse_response(res).map_err(|err| { + PrepareRequestError::RequestError(GraphQLRequestError::BodyError { + error: Box::from(format!( + "error deserializing response into output type: {err}" + )), + }) + }) + } + } + + impl PreparedRequestReqwest { + fn new( + fun: impl FnOnce(&mut PreparedArgs) -> Result, + addr: Url, + opts: GraphQlTransportOptions, + ty: &'static str, + ty_to_gql_ty_map: &TyToGqlTyMap, + ) -> Result { + let nodes = fun(&mut PreparedArgs).map_err(PrepareRequestError::FunctionError)?; + let nodes = nodes.to_select_doc(); + let nodes_len = nodes.len(); + let (doc, variables, placeholders) = build_gql_doc(ty_to_gql_ty_map, nodes, ty, None) + .map_err(PrepareRequestError::BuildError)?; + let placeholders = std::sync::Arc::new(placeholders); + Ok(Self { + doc, + variables, + nodes_len, + addr, + client: reqwest::Client::new(), + opts, + placeholders, + _phantom: PhantomData, + }) + } + + pub async fn perform( + &self, + args: impl Into>, + ) -> Result + where + K: Into, + V: serde::Serialize, + { + let args: HashMap = args.into(); + let args = args + .into_iter() + .map(|(key, val)| (key.into(), to_json_value(val))) + .collect(); + let variables = + resolve_prepared_variables(&self.placeholders, self.variables.clone(), args)?; + let req = build_gql_req(self.addr.clone(), &self.doc, &variables, &self.opts); + let req = self + .client + .request(req.method, req.addr) + .headers(req.headers) + .json(&req.body); + let req = if let Some(timeout) = self.opts.timeout { + req.timeout(timeout) + } else { + req + }; + let res = match req.send().await { + Ok(res) => { + let status = res.status(); + let headers = res.headers().clone(); + match res.json::().await { + Ok(body) => handle_response( + GraphQLResponse { + status, + headers, + body, + }, + self.nodes_len, + ) + .map_err(PrepareRequestError::RequestError)?, + Err(error) => { + return Err(PrepareRequestError::RequestError( + GraphQLRequestError::BodyError { + error: Box::new(error), + }, + )) + } + } + } + Err(error) => { + return Err(PrepareRequestError::RequestError( + GraphQLRequestError::NetworkError { + error: Box::new(error), + }, + )) + } + }; + Doc::parse_response(res).map_err(|err| { + PrepareRequestError::RequestError(GraphQLRequestError::BodyError { + error: Box::from(format!( + "error deserializing response into output type: {err}" + )), + }) + }) + } + } + + // we need a manual clone impl since the derive will + // choke if Doc isn't clone + impl Clone for PreparedRequestReqwestSync { + fn clone(&self) -> Self { + Self { + addr: self.addr.clone(), + client: self.client.clone(), + nodes_len: self.nodes_len, + doc: self.doc.clone(), + variables: self.variables.clone(), + opts: self.opts.clone(), + placeholders: self.placeholders.clone(), + _phantom: PhantomData, + } + } + } + impl Clone for PreparedRequestReqwest { + fn clone(&self) -> Self { + Self { + addr: self.addr.clone(), + client: self.client.clone(), + nodes_len: self.nodes_len, + doc: self.doc.clone(), + variables: self.variables.clone(), + opts: self.opts.clone(), + placeholders: self.placeholders.clone(), + _phantom: PhantomData, + } + } + } + + #[derive(Debug)] + pub enum PrepareRequestError { + FunctionError(BoxErr), + BuildError(GraphQLRequestError), + PlaceholderError(BoxErr), + RequestError(GraphQLRequestError), + } + + impl std::error::Error for PrepareRequestError {} + impl std::fmt::Display for PrepareRequestError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + PrepareRequestError::FunctionError(err) => { + write!(f, "error calling doc builder closure: {err}") + } + PrepareRequestError::BuildError(err) => write!(f, "error building request: {err}"), + PrepareRequestError::PlaceholderError(err) => { + write!(f, "error resolving placeholder values: {err}") + } + PrepareRequestError::RequestError(err) => { + write!(f, "error making graphql request: {err}") + } + } + } + } +} + +// +// --- --- QueryGraph types --- --- // +// + +#[derive(Clone)] +pub struct QueryGraph { + ty_to_gql_ty_map: TyToGqlTyMap, + addr: Url, +} + +impl QueryGraph { + pub fn graphql(&self) -> GraphQlTransportReqwest { + GraphQlTransportReqwest::new(self.addr.clone(), self.ty_to_gql_ty_map.clone()) + } + pub fn graphql_sync(&self) -> GraphQlTransportReqwestSync { + GraphQlTransportReqwestSync::new(self.addr.clone(), self.ty_to_gql_ty_map.clone()) + } +} + +// +// --- --- Typegraph types --- --- // +// diff --git a/libs/metagen/src/client_rs/static/lib.rs b/libs/metagen/src/client_rs/static/lib.rs new file mode 100644 index 0000000000..34b00f4242 --- /dev/null +++ b/libs/metagen/src/client_rs/static/lib.rs @@ -0,0 +1,4 @@ +// Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0. +// SPDX-License-Identifier: MPL-2.0 + +pub mod client; diff --git a/libs/metagen/src/client_ts/mod.rs b/libs/metagen/src/client_ts/mod.rs new file mode 100644 index 0000000000..83bc7d6f8e --- /dev/null +++ b/libs/metagen/src/client_ts/mod.rs @@ -0,0 +1,379 @@ +// Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0. +// SPDX-License-Identifier: MPL-2.0 + +mod node_metas; +mod selections; + +use core::fmt::Write; + +use common::typegraph::EffectType; +use shared::get_gql_type; + +use crate::interlude::*; +use crate::*; + +use crate::mdk_typescript::utils; +use crate::shared::client::*; +use crate::shared::types::NameMemo; +use crate::shared::types::TypeRenderer; +use crate::utils::GenDestBuf; +use utils::normalize_type_title; + +#[derive(Serialize, Deserialize, Debug, garde::Validate)] +pub struct ClienTsGenConfig { + #[serde(flatten)] + #[garde(dive)] + pub base: crate::config::MdkGeneratorConfigBase, +} + +impl ClienTsGenConfig { + pub fn from_json(json: serde_json::Value, workspace_path: &Path) -> anyhow::Result { + let mut config: ClienTsGenConfig = serde_json::from_value(json)?; + config.base.path = workspace_path.join(config.base.path); + config.base.typegraph_path = config + .base + .typegraph_path + .as_ref() + .map(|path| workspace_path.join(path)); + Ok(config) + } +} + +pub struct Generator { + config: ClienTsGenConfig, +} + +impl Generator { + pub const INPUT_TG: &'static str = "tg_name"; + pub fn new(config: ClienTsGenConfig) -> Result { + use garde::Validate; + config.validate(&())?; + Ok(Self { config }) + } +} + +impl crate::Plugin for Generator { + fn bill_of_inputs(&self) -> HashMap { + [( + Self::INPUT_TG.to_string(), + if let Some(tg_name) = &self.config.base.typegraph_name { + GeneratorInputOrder::TypegraphFromTypegate { + name: tg_name.clone(), + } + } else if let Some(tg_path) = &self.config.base.typegraph_path { + GeneratorInputOrder::TypegraphFromPath { + path: tg_path.clone(), + name: self.config.base.typegraph_name.clone(), + } + } else { + unreachable!() + }, + )] + .into_iter() + .collect() + } + + fn generate( + &self, + inputs: HashMap, + ) -> anyhow::Result { + let tg = match inputs + .get(Self::INPUT_TG) + .context("missing generator input")? + { + GeneratorInputResolved::TypegraphFromTypegate { raw } => raw, + GeneratorInputResolved::TypegraphFromPath { raw } => raw, + }; + let mut out = HashMap::new(); + out.insert( + self.config.base.path.join("client.ts"), + GeneratedFile { + contents: render_client_ts(&self.config, tg)?, + overwrite: true, + }, + ); + + Ok(GeneratorOutput(out)) + } +} + +fn render_client_ts(_config: &ClienTsGenConfig, tg: &Typegraph) -> anyhow::Result { + let mut client_ts = GenDestBuf { + buf: Default::default(), + }; + + writeln!( + &mut client_ts, + "// This file was @generated by metagen and is intended" + )?; + writeln!( + &mut client_ts, + "// to be generated again on subsequent metagen runs." + )?; + writeln!(&mut client_ts)?; + + render_static(&mut client_ts)?; + + let dest: &mut GenDestBuf = &mut client_ts; + let manifest = get_manifest(tg)?; + + let name_mapper = NameMapper { + nodes: tg.types.iter().cloned().map(Rc::new).collect(), + memo: Default::default(), + }; + let name_mapper = Rc::new(name_mapper); + + let node_metas = render_node_metas(dest, &manifest, name_mapper.clone())?; + let data_types = render_data_types(dest, &manifest, name_mapper.clone())?; + let data_types = Rc::new(data_types); + let selection_names = + render_selection_types(dest, &manifest, data_types.clone(), name_mapper.clone())?; + + write!( + dest, + r#" +export class QueryGraph extends _QueryGraphBase {{ + constructor() {{ + super({{"# + )?; + for (&id, ty_name) in name_mapper.memo.borrow().deref() { + let gql_ty = get_gql_type(&tg.types, id, false); + write!( + dest, + r#" + "{ty_name}": "{gql_ty}","# + )?; + } + write!( + dest, + r#" + }}); + }} + "# + )?; + + for fun in manifest.root_fns { + use heck::ToLowerCamelCase; + + let node_name = fun.name; + let method_name = node_name.to_lower_camel_case(); + let out_ty_name = data_types.get(&fun.out_id).unwrap(); + + let args_row = match ( + fun.in_id.map(|id| data_types.get(&id).unwrap()), + fun.select_ty.map(|id| selection_names.get(&id).unwrap()), + ) { + (Some(arg_ty), Some(select_ty)) => { + format!("args: {arg_ty} | PlaceholderArgs<{arg_ty}>, select: {select_ty}") + } + // functions that return scalars don't need selections + (Some(arg_ty), None) => format!("args: {arg_ty} | PlaceholderArgs<{arg_ty}>"), + // not all functions have args (empty struct arg) + (None, Some(select_ty)) => format!("select: {select_ty}"), + (None, None) => "".into(), + }; + + let args_selection = match (fun.in_id, fun.select_ty) { + (Some(_), Some(_)) => "[args, select]", + (Some(_), None) => "args", + (None, Some(_)) => "select", + (None, None) => "true", + }; + + let meta_method = node_metas + .get(&fun.id) + .map(|str| &str[..]) + .unwrap_or_else(|| "scalar"); + + let node_type = match fun.effect { + EffectType::Read => "QueryNode", + EffectType::Update | EffectType::Delete | EffectType::Create => "MutationNode", + }; + + write!( + dest, + r#" + {method_name}({args_row}) {{ + const inner = _selectionToNodeSet( + {{ "{node_name}": {args_selection} }}, + [["{node_name}", nodeMetas.{meta_method}]], + "$q", + )[0]; + return new {node_type}(inner) as {node_type}<{out_ty_name}>; + }}"# + )?; + } + writeln!( + dest, + " +}}" + )?; + + writeln!(&mut client_ts)?; + Ok(client_ts.buf) +} + +/// Render the common sections like the transports +fn render_static(dest: &mut GenDestBuf) -> core::fmt::Result { + let client_ts = include_str!("static/mod.ts"); + writeln!(dest, "{}", client_ts)?; + Ok(()) +} + +/// Render the types that'll actually hold the data, the ones +/// used for serialization +fn render_data_types( + dest: &mut GenDestBuf, + manifest: &RenderManifest, + name_mapper: Rc, +) -> anyhow::Result { + let mut renderer = TypeRenderer::new( + name_mapper.nodes.clone(), + Rc::new(mdk_typescript::types::TypescriptTypeRenderer {}), + ); + for &id in &manifest.arg_types { + _ = renderer.render(id)?; + } + for &id in &manifest.return_types { + _ = renderer.render(id)?; + } + let (types_ts, name_memo) = renderer.finalize(); + writeln!(dest.buf, "{}", types_ts)?; + Ok(name_memo) +} + +/// Render the type used for selecting fields +fn render_selection_types( + dest: &mut GenDestBuf, + manifest: &RenderManifest, + arg_types_memo: Rc, + name_mapper: Rc, +) -> Result { + let mut renderer = TypeRenderer::new( + name_mapper.nodes.clone(), + Rc::new(selections::TsNodeSelectionsRenderer { + arg_ty_names: arg_types_memo, + }), + ); + for &id in &manifest.selections { + _ = renderer.render(id)?; + } + let (buf, memo) = renderer.finalize(); + write!(dest, "{buf}")?; + Ok(memo) +} + +/// Render the `nodeMetas` object used to encode the query +/// graph metadata +fn render_node_metas( + dest: &mut GenDestBuf, + manifest: &RenderManifest, + name_mapper: Rc, +) -> Result { + let mut renderer = TypeRenderer::new( + name_mapper.nodes.clone(), + Rc::new(node_metas::TsNodeMetasRenderer { name_mapper }), + ); + for &id in &manifest.node_metas { + _ = renderer.render(id)?; + } + let (methods, memo) = renderer.finalize(); + write!( + dest, + r#" +const nodeMetas = {{ + scalar() {{ + return {{}}; + }}, + {methods} +}}; +"# + )?; + Ok(memo) +} + +struct NameMapper { + nodes: Vec>, + memo: std::cell::RefCell, +} + +impl NameMapper { + pub fn name_for(&self, id: u32) -> Rc { + self.memo + .borrow_mut() + .entry(id) + .or_insert_with(|| { + Rc::from(normalize_type_title(&self.nodes[id as usize].base().title)) + }) + .clone() + } +} + +#[test] +fn e2e() -> anyhow::Result<()> { + use crate::tests::*; + + let tg_name = "gen-test"; + let config = config::Config { + targets: [( + "default".to_string(), + config::Target( + [GeneratorConfig { + generator_name: "client_ts".to_string(), + other: serde_json::to_value(client_ts::ClienTsGenConfig { + base: config::MdkGeneratorConfigBase { + typegraph_name: Some(tg_name.into()), + typegraph_path: None, + // NOTE: root will map to the test's tempdir + path: "./".into(), + }, + })?, + }] + .into_iter() + .collect(), + ), + )] + .into_iter() + .collect(), + }; + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .thread_stack_size(16 * 1024 * 1024) + .build()? + .block_on(async { + let tg = test_typegraph_3().await?; + e2e_test(vec![E2eTestCase { + typegraphs: [(tg_name.to_string(), tg)].into_iter().collect(), + target: "default".into(), + config, + build_fn: |args| { + Box::pin(async move { + let status = tokio::process::Command::new("deno") + .args("check client.ts".split(' ').collect::>()) + .current_dir(&args.path) + .kill_on_drop(true) + .spawn()? + .wait() + .await?; + if !status.success() { + anyhow::bail!("error checking generated crate"); + } + let status = tokio::process::Command::new("deno") + .args("lint client.ts".split(' ').collect::>()) + .current_dir(&args.path) + .kill_on_drop(true) + .spawn()? + .wait() + .await?; + if !status.success() { + anyhow::bail!("error lint generated crate"); + } + Ok(()) + }) + }, + target_dir: Some("./fixtures/client_ts/".into()), + }]) + .await + })?; + Ok(()) +} diff --git a/libs/metagen/src/client_ts/node_metas.rs b/libs/metagen/src/client_ts/node_metas.rs new file mode 100644 index 0000000000..95e6414f77 --- /dev/null +++ b/libs/metagen/src/client_ts/node_metas.rs @@ -0,0 +1,183 @@ +// Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0. +// SPDX-License-Identifier: MPL-2.0 + +use std::fmt::Write; + +use common::typegraph::*; + +use super::utils::normalize_type_title; +use crate::{interlude::*, shared::types::*}; + +pub struct TsNodeMetasRenderer { + pub name_mapper: Rc, +} + +impl TsNodeMetasRenderer { + /// `props` is a map of prop_name -> (TypeName, subNodeName) + fn render_for_object( + &self, + dest: &mut impl Write, + ty_name: &str, + props: IndexMap>, + ) -> std::fmt::Result { + write!( + dest, + r#" + {ty_name}(): NodeMeta {{ + return {{ + subNodes: ["# + )?; + for (key, node_ref) in props { + write!( + dest, + r#" + ["{key}", nodeMetas.{node_ref}],"# + )?; + } + write!( + dest, + r#" + ], + }}; + }},"# + )?; + Ok(()) + } + + fn render_for_func( + &self, + dest: &mut impl Write, + ty_name: &str, + return_node: &str, + argument_fields: Option>>, + ) -> std::fmt::Result { + write!( + dest, + r#" + {ty_name}(): NodeMeta {{ + return {{ + ...nodeMetas.{return_node}(),"# + )?; + if let Some(fields) = argument_fields { + write!( + dest, + r#" + argumentTypes: {{"# + )?; + + for (key, ty) in fields { + write!( + dest, + r#" + {key}: "{ty}","# + )?; + } + + write!( + dest, + r#" + }},"# + )?; + } + write!( + dest, + r#" + }}; + }},"# + )?; + Ok(()) + } +} + +impl RenderType for TsNodeMetasRenderer { + fn render( + &self, + renderer: &mut TypeRenderer, + cursor: &mut VisitCursor, + ) -> anyhow::Result { + use heck::ToPascalCase; + + let name = match cursor.node.clone().deref() { + TypeNode::Any { .. } => unimplemented!("Any type support not implemented"), + TypeNode::Boolean { .. } + | TypeNode::Float { .. } + | TypeNode::Integer { .. } + | TypeNode::String { .. } + | TypeNode::File { .. } => "scalar".into(), + // list and optional node just return the meta of the wrapped type + TypeNode::Optional { + data: OptionalTypeData { item, .. }, + .. + } + | TypeNode::List { + data: ListTypeData { items: item, .. }, + .. + } => renderer.render_subgraph(*item, cursor)?.0.unwrap().to_string(), + TypeNode::Function { data, base } => { + let (return_ty_name, _cyclic) = renderer.render_subgraph(data.output, cursor)?; + let return_ty_name = return_ty_name.unwrap() ; + let props = match renderer.nodes[data.input as usize].deref() { + TypeNode::Object { data, .. } if !data.properties.is_empty() => { + let props = data + .properties + .iter() + // generate property types first + .map(|(name, &dep_id)| { + eyre::Ok((name.clone(), self.name_mapper.name_for(dep_id))) + }) + .collect::, _>>()?; + Some(props) + } + _ => None, + }; + let node_name = &base.title; + let ty_name = normalize_type_title(node_name).to_pascal_case(); + self.render_for_func(renderer, &ty_name, &return_ty_name, props)?; + ty_name + } + TypeNode::Object { data, base } => { + let props = data + .properties + .iter() + // generate property types first + .map(|(name, &dep_id)| { + let (ty_name, _cyclic) = renderer.render_subgraph(dep_id, cursor)?; + let ty_name = ty_name.unwrap(); + eyre::Ok((name.clone(), ty_name)) + }) + .collect::, _>>()?; + let node_name = &base.title; + let ty_name = normalize_type_title(node_name).to_pascal_case(); + self.render_for_object(renderer, &ty_name, props)?; + ty_name + } + TypeNode::Either { + .. + // data: EitherTypeData { one_of: variants }, + // base, + } + | TypeNode::Union { + .. + // data: UnionTypeData { any_of: variants }, + // base, + } => { + // let variants = variants + // .iter() + // .map(|&inner| { + // let (ty_name, _cyclic) = renderer.render_subgraph(inner, cursor)?; + // let ty_name = match ty_name { + // RenderedName::Name(name) => name, + // RenderedName::Placeholder(name) => name, + // }; + // Ok::<_, anyhow::Error>(ty_name) + // }) + // .collect::, _>>()?; + // let ty_name = normalize_type_title(&base.title); + // self.render_union_type(renderer, &ty_name, variants)?; + // ty_name + todo!("unions are wip") + } + }; + Ok(name) + } +} diff --git a/libs/metagen/src/client_ts/selections.rs b/libs/metagen/src/client_ts/selections.rs new file mode 100644 index 0000000000..bec3f0aa39 --- /dev/null +++ b/libs/metagen/src/client_ts/selections.rs @@ -0,0 +1,117 @@ +// Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0. +// SPDX-License-Identifier: MPL-2.0 + +use std::fmt::Write; + +use common::typegraph::*; + +use super::utils::*; +use crate::{interlude::*, shared::client::*, shared::types::*}; + +pub struct TsNodeSelectionsRenderer { + pub arg_ty_names: Rc, +} + +impl TsNodeSelectionsRenderer { + /// `props` is a map of prop_name -> (SelectionType, ArgumentType) + fn render_for_object( + &self, + dest: &mut impl Write, + ty_name: &str, + props: IndexMap, + ) -> std::fmt::Result { + writeln!( + dest, + "export type {ty_name} = {{ + _?: SelectionFlags;" + )?; + for (name, select_ty) in props { + use SelectionTy::*; + match select_ty { + Scalar => writeln!(dest, r#" {name}?: ScalarSelectNoArgs;"#)?, + ScalarArgs { arg_ty } => { + writeln!(dest, r#" {name}?: ScalarSelectArgs<{arg_ty}>;"#)? + } + Composite { select_ty } => { + writeln!(dest, r#" {name}?: CompositeSelectNoArgs<{select_ty}>;"#)? + } + CompositeArgs { arg_ty, select_ty } => writeln!( + dest, + r#" {name}?: CompositeSelectArgs<{arg_ty}, {select_ty}>;"# + )?, + }; + } + writeln!(dest, "}};")?; + Ok(()) + } +} + +impl RenderType for TsNodeSelectionsRenderer { + fn render( + &self, + renderer: &mut TypeRenderer, + cursor: &mut VisitCursor, + ) -> anyhow::Result { + use heck::ToPascalCase; + + let name = match cursor.node.clone().deref() { + TypeNode::Boolean { .. } + | TypeNode::Float { .. } + | TypeNode::Integer { .. } + | TypeNode::String { .. } + | TypeNode::File { .. } => unreachable!("scalars don't get to have selections"), + TypeNode::Any { .. } => unimplemented!("Any type support not implemented"), + TypeNode::Optional { data: OptionalTypeData { item, .. }, .. } + | TypeNode::List { data: ListTypeData { items: item, .. }, .. } + | TypeNode::Function { data:FunctionTypeData { output:item,.. }, .. } + => renderer.render_subgraph(*item, cursor)?.0.unwrap().to_string(), + TypeNode::Object { data, base } => { + let props = data + .properties + .iter() + // generate property types first + .map(|(name, &dep_id)| { + eyre::Ok( + ( + normalize_struct_prop_name(name), + selection_for_field(dep_id, &self.arg_ty_names, renderer, cursor)? + ) + ) + }) + .collect::, _>>()?; + let node_name = &base.title; + let ty_name = normalize_type_title(node_name); + let ty_name = format!("{ty_name}Selections").to_pascal_case(); + self.render_for_object(renderer, &ty_name, props)?; + ty_name + } + TypeNode::Either { + .. + // data: EitherTypeData { one_of: variants }, + // base, + } + | TypeNode::Union { + .. + // data: UnionTypeData { any_of: variants }, + // base, + } => { + // let variants = variants + // .iter() + // .map(|&inner| { + // let (ty_name, _cyclic) = renderer.render_subgraph(inner, cursor)?; + // let ty_name = match ty_name { + // RenderedName::Name(name) => name, + // RenderedName::Placeholder(name) => name, + // }; + // Ok::<_, anyhow::Error>(ty_name) + // }) + // .collect::, _>>()?; + // let ty_name = normalize_type_title(&base.title); + // self.render_union_type(renderer, &ty_name, variants)?; + // ty_name + todo!("unions are wip") + } + }; + Ok(name) + } +} diff --git a/libs/metagen/src/client_ts/static/mod.ts b/libs/metagen/src/client_ts/static/mod.ts new file mode 100644 index 0000000000..9b72775235 --- /dev/null +++ b/libs/metagen/src/client_ts/static/mod.ts @@ -0,0 +1,639 @@ +function _selectionToNodeSet( + selection: Selection, + metas: [string, () => NodeMeta][], + parentPath: string, +): SelectNode[] { + const out = [] as SelectNode[]; + const selectAll = selection._ == "selectAll"; + // set of the user specified nodes to do sanity + // check at the end + const foundNodes = new Set(Object.keys(selection)); + + for ( + const [nodeName, metaFn] of metas + ) { + foundNodes.delete(nodeName); + + const nodeSelection = selection[nodeName]; + if (!nodeSelection && !selectAll) { + // this node was not selected + continue; + } + + const { argumentTypes, subNodes } = metaFn(); + + const nodeInstances = nodeSelection instanceof Alias + ? nodeSelection.aliases() + : { [nodeName]: nodeSelection }; + + for ( + const [instanceName, instanceSelection] of Object.entries(nodeInstances) + ) { + if (!instanceSelection && !selectAll) { + continue; + } + if (instanceSelection instanceof Alias) { + throw new Error( + `nested Alias discovored at ${parentPath}.${instanceName}`, + ); + } + const node: SelectNode = { instanceName, nodeName }; + + if (argumentTypes) { + // make sure the arg is of the expected form + let arg = instanceSelection; + if (Array.isArray(arg)) { + arg = arg[0]; + } + // TODO: consider bringing in Zod (after hoisting impl into common lib) + if (typeof arg != "object" || arg === null) { + throw new Error( + `node at ${parentPath}.${instanceName} is a node ` + + `that requires arguments object but detected argument ` + + `is typeof ${typeof arg}`, + ); + } + + const expectedArguments = new Map(Object.entries(argumentTypes)); + node.args = {}; + for (const [key, value] of Object.entries(arg)) { + const typeName = expectedArguments.get(key); + // TODO: consider logging a warning if `_` is detected incase user passes + // Selection as arg + if (!typeName) { + throw new Error( + `unexpected argument ${key} at ${parentPath}.${instanceName}`, + ); + } + expectedArguments.delete(key); + node.args[key] = { typeName, value }; + } + } + + if (subNodes) { + // sanity check selection object + let subSelections = instanceSelection; + if (argumentTypes) { + if (!Array.isArray(subSelections)) { + throw new Error( + `node at ${parentPath}.${instanceName} ` + + `is a composite that takes an argument ` + + `but selection is typeof ${typeof subSelections}`, + ); + } + subSelections = subSelections[1]; + } else if (Array.isArray(subSelections)) { + throw new Error( + `node at ${parentPath}.${instanceName} ` + + `is a composite that takes no arguments ` + + `but selection is typeof ${typeof subSelections}`, + ); + } + if (typeof subSelections != "object") { + throw new Error( + `node at ${parentPath}.${nodeName} ` + + `is a no argument composite but first element of ` + + `selection is typeof ${typeof nodeSelection}`, + ); + } + + node.subNodes = _selectionToNodeSet( + // assume it's a Selection. If it's an argument + // object, mismatch between the node desc should hopefully + // catch it + subSelections as Selection, + subNodes, + `${parentPath}.${instanceName}`, + ); + } + + out.push(node); + } + } + foundNodes.delete("_"); + if (foundNodes.size > 0) { + throw new Error( + `unexpected nodes found in selection set at ${parentPath}: ${[ + ...foundNodes, + ]}`, + ); + } + return out; +} + +/* Query node types section */ + +type SelectNode<_Out = unknown> = { + nodeName: string; + instanceName: string; + args?: NodeArgs; + subNodes?: SelectNode[]; +}; + +export class QueryNode { + #inner: SelectNode; + constructor( + inner: SelectNode, + ) { + this.#inner = inner; + } + + inner() { + return this.#inner; + } +} + +export class MutationNode { + #inner: SelectNode; + constructor( + inner: SelectNode, + ) { + this.#inner = inner; + } + + inner() { + return this.#inner; + } +} + +type SelectNodeOut = T extends (QueryNode | MutationNode) + ? O + : never; +type QueryDocOut = T extends + Record | MutationNode> ? { + [K in keyof T]: SelectNodeOut; + } + : never; + +type NodeMeta = { + subNodes?: [string, () => NodeMeta][]; + argumentTypes?: { [name: string]: string }; +}; + +/* Selection types section */ + +type SelectionFlags = "selectAll"; + +type Selection = { + _?: SelectionFlags; + [key: string]: + | SelectionFlags + | ScalarSelectNoArgs + | ScalarSelectArgs> + | CompositeSelectNoArgs + | CompositeSelectArgs, Selection> + | Selection; +}; + +type ScalarSelectNoArgs = + | boolean + | Alias + | null + | undefined; + +type ScalarSelectArgs> = + | ArgT + | PlaceholderArgs + | Alias> + | false + | null + | undefined; + +type CompositeSelectNoArgs = + | SelectionT + | Alias + | false + | null + | undefined; + +type CompositeSelectArgs, SelectionT> = + | [ArgT | PlaceholderArgs, SelectionT] + | Alias<[ArgT | PlaceholderArgs, SelectionT]> + | false + | undefined + | null; + +/** + * Request multiple instances of a single node under different + * aliases. Look at {@link alias} for a functional way of instantiating + * this class. + */ +export class Alias { + #aliases: Record; + constructor( + aliases: Record, + ) { + this.#aliases = aliases; + } + aliases() { + return this.#aliases; + } +} + +/** + * Request multiple instances of a single node under different + * aliases. + */ +export function alias(aliases: Record): Alias { + return new Alias(aliases); +} + +/* Argument types section */ + +type NodeArgValue = { + typeName: string; + value: unknown; +}; + +type NodeArgs = { + [name: string]: NodeArgValue; +}; + +/** + * This object is passed to closures used for preparing requests + * ahead of time for {@link PreparedRequest}s. It allows one to + * get {@link PlaceholderValue}s that can be used in place of node + * arguments. At request time, the {@link PreparedRequest} then + * takes an object that adheres to `T` that can then be used + * to replace the placeholders. + */ +export class PreparedArgs> { + get(key: OnlyStringKeys): PlaceholderValue { + return new PlaceholderValue(key); + } +} + +/** + * Placeholder values for use by {@link PreparedRequest} + */ +export class PlaceholderValue<_T> { + #key: string; + constructor(key: string) { + this.#key = key; + } + + key() { + return this.#key; + } +} + +export type PlaceholderArgs> = { + [K in keyof T]: PlaceholderValue; +}; + +/* GraphQL section */ + +/** + * Options to be used for requests performed by {@link GraphQLTransport}. + */ +export type GraphQlTransportOptions = Omit & { + /** + * {@link fetch} implementaiton to use. Defaults to the one found in the environment + */ + fetch?: typeof fetch; +}; + +function convertQueryNodeGql( + node: SelectNode, + variables: Map, +) { + let out = node.nodeName == node.instanceName + ? node.nodeName + : `${node.instanceName}: ${node.nodeName}`; + + const args = node.args; + if (args) { + out = `${out} (${ + Object.entries(args) + .map(([key, val]) => { + const name = `in${variables.size}`; + variables.set(name, val); + return `${key}: $${name}`; + }) + .join(", ") + })`; + } + + const subNodes = node.subNodes; + if (subNodes) { + out = `${out} { ${ + subNodes.map((node) => convertQueryNodeGql(node, variables)).join(" ") + } }`; + } + return out; +} + +function buildGql( + typeToGqlTypeMap: Record, + query: Record, + ty: "query" | "mutation", + name: string = "", +) { + const variables = new Map(); + + const rootNodes = Object + .entries(query) + .map(([key, node]) => { + const fixedNode = { ...node, instanceName: key }; + return convertQueryNodeGql(fixedNode, variables); + }) + .join("\n "); + + let argsRow = [...variables.entries()] + .map(([key, val]) => `$${key}: ${typeToGqlTypeMap[val.typeName]}`) + .join(", "); + if (argsRow.length > 0) { + // graphql doesn't like empty parentheses so we only + // add them if there are args + argsRow = `(${argsRow})`; + } + + const doc = `${ty} ${name}${argsRow} { + ${rootNodes} +}`; + return { + doc, + variables: Object.fromEntries( + [...variables.entries()] + .map(([key, val]) => [key, val.value]), + ), + }; +} + +async function fetchGql( + addr: URL, + doc: string, + variables: Record, + options: GraphQlTransportOptions, +) { + // console.log(doc, variables); + const fetchImpl = options.fetch ?? fetch; + const res = await fetchImpl(addr, { + ...options, + method: "POST", + headers: { + accept: "application/json", + "content-type": "application/json", + ...options.headers ?? {}, + }, + body: JSON.stringify({ + query: doc, + variables, + }), + }); + if (!res.ok) { + const body = await res.text().catch((err) => `error reading body: ${err}`); + throw new (Error as ErrorPolyfill)( + `graphql request to ${addr} failed with status ${res.status}: ${body}`, + { + cause: { + response: res, + body, + }, + }, + ); + } + if (res.headers.get("content-type") != "application/json") { + throw new (Error as ErrorPolyfill)( + "unexpected content type in response", + { + cause: { + response: res, + body: await res.text().catch((err) => `error reading body: ${err}`), + }, + }, + ); + } + return await res.json() as { data: unknown; errors?: object[] }; +} + +/** + * Access the typegraph over it's exposed GraphQL API. + */ +export class GraphQLTransport { + constructor( + public address: URL, + public options: GraphQlTransportOptions, + private typeToGqlTypeMap: Record, + ) { + } + + async #request( + doc: string, + variables: Record, + options?: GraphQlTransportOptions, + ) { + const res = await fetchGql(this.address, doc, variables, { + ...this.options, + ...options, + }); + if ("errors" in res) { + throw new (Error as ErrorPolyfill)("graphql errors on response", { + cause: res.errors, + }); + } + return res.data; + } + + /** + * Make a query request to the typegraph. + */ + async query>>( + query: Doc, + { options, name = "" }: { + options?: GraphQlTransportOptions; + name?: string; + } = {}, + ): Promise> { + const { variables, doc } = buildGql( + this.typeToGqlTypeMap, + Object.fromEntries( + Object.entries(query).map(( + [key, val], + ) => [key, (val as QueryNode).inner()]), + ), + "query", + name, + ); + return await this.#request(doc, variables, options) as QueryDocOut; + } + + /** + * Make a mutation request to the typegraph. + */ + async mutation>>( + query: Doc, + { options, name = "" }: { + options?: GraphQlTransportOptions; + name?: string; + } = {}, + ): Promise> { + const { variables, doc } = buildGql( + this.typeToGqlTypeMap, + Object.fromEntries( + Object.entries(query).map(( + [key, val], + ) => [key, (val as MutationNode).inner()]), + ), + "mutation", + name, + ); + return await this.#request(doc, variables, options) as QueryDocOut; + } + + /** + * Prepare an ahead of time query {@link PreparedRequest}. + */ + prepareQuery< + T extends JsonObject, + Doc extends Record>, + >( + fun: (args: PreparedArgs) => Doc, + { name = "" }: { name?: string } = {}, + ): PreparedRequest { + return new PreparedRequest( + this.address, + this.options, + this.typeToGqlTypeMap, + fun, + "query", + name, + ); + } + + /** + * Prepare an ahead of time mutation {@link PreparedRequest}. + */ + prepareMutation< + T extends JsonObject, + Q extends Record>, + >( + fun: (args: PreparedArgs) => Q, + { name = "" }: { name?: string } = {}, + ): PreparedRequest { + return new PreparedRequest( + this.address, + this.options, + this.typeToGqlTypeMap, + fun, + "mutation", + name, + ); + } +} + +/** + * Prepares the GraphQL string ahead of time and allows re-use + * avoid the compute and garbage overhead of re-building it for + * repeat queries. + */ +export class PreparedRequest< + T extends JsonObject, + Doc extends Record | MutationNode>, +> { + public doc: string; + #mappings: Record; + + constructor( + private address: URL, + private options: GraphQlTransportOptions, + typeToGqlTypeMap: Record, + fun: (args: PreparedArgs) => Doc, + ty: "query" | "mutation", + name: string = "", + ) { + const args = new PreparedArgs(); + const dryRunNode = fun(args); + const { doc, variables } = buildGql( + typeToGqlTypeMap, + Object.fromEntries( + Object.entries(dryRunNode).map(( + [key, val], + ) => [key, (val as MutationNode).inner()]), + ), + ty, + name, + ); + this.doc = doc; + this.#mappings = variables; + } + + resolveVariables( + args: T, + mappings: Record, + ) { + const resolvedVariables = {} as Record; + for (const [key, val] of Object.entries(mappings)) { + if (val instanceof PlaceholderValue) { + resolvedVariables[key] = args[val.key()]; + } else if (typeof val == "object" && val != null) { + this.resolveVariables(args, val as JsonObject); + } else { + resolvedVariables[key] = val; + } + } + return resolvedVariables; + } + + /** + * Execute the prepared request. + */ + async perform(args: T, opts?: GraphQlTransportOptions): Promise< + { + [K in keyof Doc]: SelectNodeOut; + } + > { + const resolvedVariables = this.resolveVariables(args, this.#mappings); + // console.log(this.doc, { + // resolvedVariables, + // mapping: this.#mappings, + // }); + const res = await fetchGql( + this.address, + this.doc, + resolvedVariables, + { + ...this.options, + ...opts, + }, + ); + if ("errors" in res) { + throw new (Error as ErrorPolyfill)("graphql errors on response", { + cause: res.errors, + }); + } + return res.data as QueryDocOut; + } +} + +/* Util types section */ + +type OnlyStringKeys> = { + [K in keyof T]: K extends string ? K : never; +}[keyof T]; + +type JsonLiteral = string | number | boolean | null; +type JsonObject = { [key: string]: Json }; +type JsonArray = Json[]; +type Json = JsonLiteral | JsonObject | JsonArray; + +type ErrorPolyfill = new (msg: string, payload: unknown) => Error; + +/* QueryGraph section */ + +class _QueryGraphBase { + constructor(private typeNameMapGql: Record) {} + + /** + * Get the {@link GraphQLTransport} for the typegraph. + */ + graphql(addr: URL | string, options?: GraphQlTransportOptions) { + return new GraphQLTransport( + new URL(addr), + options ?? {}, + this.typeNameMapGql, + ); + } +} + +// -------------------------------------------------- // diff --git a/libs/metagen/src/lib.rs b/libs/metagen/src/lib.rs index 3a16f319bf..dfa4339902 100644 --- a/libs/metagen/src/lib.rs +++ b/libs/metagen/src/lib.rs @@ -6,14 +6,14 @@ mod interlude { pub use common::typegraph::TypeNode; pub use common::typegraph::Typegraph; - pub use std::collections::{HashMap, HashSet}; + pub use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; pub use std::ops::Deref; pub use std::path::{Path, PathBuf}; pub use std::rc::Rc; pub use std::sync::Arc; pub use color_eyre::eyre::{ - self as anyhow, bail, ensure, format_err, ContextCompat, OptionExt, Result, WrapErr, + self, self as anyhow, bail, ensure, format_err, ContextCompat, OptionExt, Result, WrapErr, }; pub use futures_concurrency::prelude::*; pub use indexmap::IndexMap; @@ -22,15 +22,23 @@ mod interlude { pub use serde::{Deserialize, Serialize}; #[cfg(test)] pub use tokio::process::Command; + + pub use crate::anyhow_to_eyre; } mod config; -mod mdk; +mod macros; +mod shared; + mod mdk_python; mod mdk_rust; mod mdk_substantial; mod mdk_typescript; +mod client_py; +mod client_rs; +mod client_ts; + #[cfg(test)] mod tests; mod utils; @@ -155,6 +163,36 @@ impl GeneratorRunner { }, }, ), + ( + "client_ts".to_string(), + GeneratorRunner { + op: |workspace_path: &Path, val| { + let config = client_ts::ClienTsGenConfig::from_json(val, workspace_path)?; + let generator = client_ts::Generator::new(config)?; + Ok(Box::new(generator)) + }, + }, + ), + ( + "client_py".to_string(), + GeneratorRunner { + op: |workspace_path: &Path, val| { + let config = client_py::ClienPyGenConfig::from_json(val, workspace_path)?; + let generator = client_py::Generator::new(config)?; + Ok(Box::new(generator)) + }, + }, + ), + ( + "client_rs".to_string(), + GeneratorRunner { + op: |workspace_path: &Path, val| { + let config = client_rs::ClienRsGenConfig::from_json(val, workspace_path)?; + let generator = client_rs::Generator::new(config)?; + Ok(Box::new(generator)) + }, + }, + ), ]); } diff --git a/libs/metagen/src/macros.rs b/libs/metagen/src/macros.rs new file mode 100644 index 0000000000..240fe79e88 --- /dev/null +++ b/libs/metagen/src/macros.rs @@ -0,0 +1,22 @@ +// Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0. +// SPDX-License-Identifier: MPL-2.0 + +// Conversion that preserves source chain +// but not backtraces. +// This can be made a funciton but we have to +// depend on anyhow directly to be able to refer +// to it's Error type. +// https://github.com/eyre-rs/eyre/issues/31 +#[macro_export] +macro_rules! anyhow_to_eyre { + () => { + |err| eyre::format_err!(Box::new(err)) + }; +} + +#[macro_export] +macro_rules! map_ferr { + () => { + |err| eyre::format_err!(err) + }; +} diff --git a/libs/metagen/src/mdk/types.rs b/libs/metagen/src/mdk/types.rs deleted file mode 100644 index 7abc6725a8..0000000000 --- a/libs/metagen/src/mdk/types.rs +++ /dev/null @@ -1,295 +0,0 @@ -// Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0. -// SPDX-License-Identifier: MPL-2.0 - -use std::fmt::Write; - -use common::typegraph::*; - -use crate::{interlude::*, utils::GenDestBuf}; - -pub type VisitedTypePaths = HashMap>>; - -/// This type tracks the type graph traversal path. -pub struct VisitCursor { - pub id: u32, - pub node: Rc, - pub path: Vec, - pub visited_path: VisitedTypePaths, -} - -/// Trait to provides implementation for a lanaguage specific -/// type code generation -/// Used by [TypeRenderer]. -pub trait RenderType { - /// The [TypeRenderer] does it's job in two phases. - /// This phase implements the first phase which is generation of type names. - /// - /// To assist in generating names that might to refer to other types inline - /// like Optional, Placeholder names can be used instead. - /// This comes in handly when dealing with recursive types. - fn render_name(&self, renderer: &mut TypeRenderer, visit_cursor: &VisitCursor) -> RenderedName; - - /// The [TypeRenderer] does it's job in two phases. - /// This phase implements the second phase which is generation of type names. - /// This phase is used if only the [type_body_required] returns true. - /// - /// Along with the [VisitCursor], the name generated from [render_name] will - /// be passed (either the real name or the auto-placeholder). It's upto the - /// implementation to replace placeholders (using [TypeRenderer.replace_placeholder_ty_name]) - /// mayhaps. - /// - /// [TypeRenderer] implements [core::fmt::Write] and the implementation is expected - /// to write the generated code into it but it may choose not to do so. Types that refer - /// others can call [TypeRenderer.render_subgraph] to render other types first. The - /// method returns a flag to notify if the subgraph was cyclic to the current type and - /// the implementaiton may correct accordingly. - fn render_body( - &self, - renderer: &mut TypeRenderer, - ty_name: &str, - visit_cursor: &mut VisitCursor, - ) -> anyhow::Result<()>; - - /// Weather or not to use [`render_body`] for the specified type. - /// The default implementation returns true for all but the simple primitive - /// types. Whatmore, it returns true if the primitive has a user defined alias, - /// type validator and other interesting metadata. - fn type_body_required(&self, node: Rc) -> bool { - match node.deref() { - // functions will be absent in our gnerated types - TypeNode::Function { .. } => false, - // under certain conditionds, we don't want to generate aliases - // for primitive types. this includes - // - types with default generated names - // - types with no special semantics - TypeNode::Boolean { base } if base.title.starts_with("boolean_") => false, - TypeNode::Integer { - base, - data: - common::typegraph::IntegerTypeData { - minimum: None, - maximum: None, - multiple_of: None, - exclusive_minimum: None, - exclusive_maximum: None, - }, - } if base.title.starts_with("integer_") => false, - TypeNode::Float { - base, - data: - FloatTypeData { - minimum: None, - maximum: None, - multiple_of: None, - exclusive_minimum: None, - exclusive_maximum: None, - }, - } if base.title.starts_with("float_") => false, - TypeNode::String { - base: - TypeNodeBase { - enumeration: None, - title, - .. - }, - data: - StringTypeData { - min_length: None, - max_length: None, - format: None, - pattern: None, - }, - } if title.starts_with("string_") => false, - TypeNode::File { - base, - data: - FileTypeData { - min_size: None, - max_size: None, - mime_types: None, - }, - } if base.title.starts_with("file_") => false, - _ => true, - } - } - - /// Wether or not the parent node was visited when a part of the graph - /// was traversed in an _unfortunate_ manner. This is more than a cyclic - /// test but a cycle that's _unfortunate_. If the implementaiton is - /// resistant to certain types of cycles, it can return false to signal - /// that the cycle is fortunate and doesn't need special handling. - /// - /// The default implementation treats all cycles as unfortunate excepts - /// those that are broken by a List type. - fn is_path_unfortunately_cyclic( - &self, - renderer: &TypeRenderer, - parent_cursor: &VisitCursor, - current_cursor: &VisitCursor, - ) -> Option { - current_cursor - .visited_path - .get(&parent_cursor.id) - .map(|cyclic_paths| { - // for all cycles that lead back to current - cyclic_paths - .iter() - .map(|path| { - path[parent_cursor.path.len()..] - .iter() - // until we arrive at parent - .take_while(|&&dep_id| dep_id != parent_cursor.id) - // see if any are lists - .any(|&dep_id| { - matches!( - renderer.nodes[dep_id as usize].deref(), - TypeNode::List { .. } - ) - }) - }) - // we know this whole branch is unsized if - // any one of the paths don't contain a - // type stored on the heap i.e. a vec - .any(|has_list| !has_list) - }) - } -} - -pub type NameMemo = HashMap>; - -/// Helper for generating type bodies that's cycle aware. -pub struct TypeRenderer { - dest: GenDestBuf, - nodes: Vec>, - name_memo: NameMemo, - render_type: Rc, - replacement_records: Vec<(u32, Vec<(String, String)>)>, -} - -pub enum RenderedName { - Name(String), - Placeholder, -} - -impl TypeRenderer { - pub fn new(nodes: &[TypeNode], render_type: Rc) -> Self { - Self { - dest: GenDestBuf { - buf: Default::default(), - }, - name_memo: Default::default(), - nodes: nodes.iter().cloned().map(Rc::new).collect(), - render_type, - replacement_records: vec![], - } - } - - pub fn replace_placeholder_ty_name( - &mut self, - id: u32, - replacement: Rc, - mut replacement_records: Vec<(String, String)>, - ) { - let old_name = self.name_memo.insert(id, replacement.clone()).unwrap(); - replacement_records.insert(0, (old_name.to_string(), replacement.to_string())); - // dbg!((&id, &replacement_records)); - self.replacement_records.push((id, replacement_records)); - } - - pub fn render(&mut self, id: u32) -> anyhow::Result> { - let (name, _) = self.render_subgraph( - id, - &mut VisitCursor { - id: u32::MAX, - node: self.nodes[0].clone(), - path: vec![], - visited_path: Default::default(), - }, - )?; - Ok(name) - } - - pub fn render_subgraph( - &mut self, - id: u32, - parent_cursor: &mut VisitCursor, - ) -> anyhow::Result<(Rc, Option)> { - let my_path: Vec<_> = parent_cursor - .path - .iter() - .copied() - .chain(std::iter::once(id)) - .collect(); - - let node = self.nodes[id as usize].clone(); - - let mut current_cursor = VisitCursor { - id, - node: node.clone(), - visited_path: [(id, vec![my_path.clone()])].into_iter().collect(), - path: my_path, - }; - - // short circuit if we've already generated the type - let ty_name = if let Some(name) = self.name_memo.get(&id) { - name.clone() - } else { - let render_type_impl = self.render_type.clone(); - - // generate the type name up first - let ty_name = render_type_impl.render_name(self, ¤t_cursor); - let is_placeholder = matches!(ty_name, RenderedName::Placeholder); - let ty_name: Rc = match ty_name { - RenderedName::Name(name) => name.into(), - RenderedName::Placeholder => format!("&&placeholder{id}%%").into(), - }; - - // insert typename into memo before generation to allow cyclic resolution - // if this function is recursively called when generating dependent branches - self.name_memo.insert(id, ty_name.clone()); - - if render_type_impl.type_body_required(node) { - render_type_impl.render_body(self, &ty_name, &mut current_cursor)?; - } - if is_placeholder && self.name_memo.get(&id) == Some(&ty_name) { - bail!("placeholder name generated for \"{id}\" ({ty_name}) was not replaced by RenderType impl"); - } - ty_name - }; - - let cyclic = - self.render_type - .is_path_unfortunately_cyclic(self, parent_cursor, ¤t_cursor); - for (id, paths) in current_cursor.visited_path { - parent_cursor - .visited_path - .entry(id) - .or_default() - .extend(paths); - } - Ok((ty_name, cyclic)) - } - - pub fn finalize(self) -> (String, NameMemo) { - let mut out = self.dest.buf; - for (_id, records) in self.replacement_records.into_iter().rev() { - // dbg!((&_id, &records)); - for (from, to) in records { - out = out.replace(&from, &to); - } - } - (out, self.name_memo) - } -} - -impl Write for TypeRenderer { - #[inline] - fn write_str(&mut self, s: &str) -> core::fmt::Result { - ::write_str(&mut self.dest, s) - } - - #[inline] - fn write_char(&mut self, c: char) -> core::fmt::Result { - ::write_char(&mut self.dest, c) - } -} diff --git a/libs/metagen/src/mdk_python/mod.rs b/libs/metagen/src/mdk_python/mod.rs index 4ce236bcb9..d7670f4b7b 100644 --- a/libs/metagen/src/mdk_python/mod.rs +++ b/libs/metagen/src/mdk_python/mod.rs @@ -5,9 +5,10 @@ use garde::external::compact_str::CompactStringExt; use heck::ToPascalCase; +use indexmap::IndexSet; use crate::interlude::*; -use crate::mdk::*; +use crate::shared::*; use crate::*; use self::utils::Memo; @@ -211,7 +212,7 @@ fn render_main( required: &MergedRequiredObjects, file_stem: &str, ) -> anyhow::Result { - let mut exports = HashSet::new(); + let mut exports = std::collections::BTreeSet::new(); for func in required.funcs.iter() { exports.insert(format!("typed_{}", func.name)); exports.insert(func.input_name.clone()); @@ -321,8 +322,8 @@ fn merge_requirements( memo: Memo::new(), top_level_types: vec![], }; - let mut types = HashSet::new(); - let mut funcs = HashSet::new(); + let mut types = IndexSet::new(); + let mut funcs = IndexSet::new(); for req in requirements { // merge classes diff --git a/libs/metagen/src/mdk_python/static/types.py.jinja b/libs/metagen/src/mdk_python/static/types.py.jinja index b79aafca65..8a0fe582c5 100644 --- a/libs/metagen/src/mdk_python/static/types.py.jinja +++ b/libs/metagen/src/mdk_python/static/types.py.jinja @@ -4,7 +4,7 @@ from dataclasses import dataclass, asdict, fields FORWARD_REFS = {} -class Struct(): +class Struct: def repr(self): return asdict(self) @@ -76,21 +76,22 @@ class Struct(): except Exception: return val + {% for class in classes -%} {{ class.def }} - FORWARD_REFS["{{ class.hint }}"] = {{ class.hint }} -{% endfor -%} + +{% endfor -%} {% for def in types -%} {{ def }} {% endfor %} - def __repr(value: Any): if isinstance(value, Struct): return value.repr() return value + {%for func in funcs %} def typed_{{ func.name }}(user_fn: Callable[[{{ func.input_name }}], {{ func.output_name }}]): def exported_wrapper(raw_inp): @@ -99,6 +100,7 @@ def typed_{{ func.name }}(user_fn: Callable[[{{ func.input_name }}], {{ func.out if isinstance(out, list): return [__repr(v) for v in out] return __repr(out) + return exported_wrapper {% endfor %} diff --git a/libs/metagen/src/mdk_python/types.rs b/libs/metagen/src/mdk_python/types.rs index 521bfb87e1..96631a1240 100644 --- a/libs/metagen/src/mdk_python/types.rs +++ b/libs/metagen/src/mdk_python/types.rs @@ -98,7 +98,7 @@ fn visit_union_or_either( tg: &Typegraph, ) -> anyhow::Result { let mut visit_variants = |variants: &[u32]| -> anyhow::Result { - let mut variants_repr = HashSet::new(); + let mut variants_repr = std::collections::BTreeSet::new(); for idx in variants.iter() { let field_tpe = &tg.types[*idx as usize]; let type_repr = visit_type(tera, memo, field_tpe, tg)?.hint; diff --git a/libs/metagen/src/mdk_rust/mod.rs b/libs/metagen/src/mdk_rust/mod.rs index 860e1b916a..6d8e992c35 100644 --- a/libs/metagen/src/mdk_rust/mod.rs +++ b/libs/metagen/src/mdk_rust/mod.rs @@ -11,11 +11,11 @@ //! - Will not be replaced on second generation. mod stubs; -mod types; -mod utils; +pub mod types; +pub mod utils; use crate::interlude::*; -use crate::mdk::*; +use crate::shared::*; use crate::utils::*; use crate::*; @@ -149,13 +149,13 @@ fn gen_mod_rs(config: &MdkRustGenConfig, tg: &Typegraph) -> anyhow::Result>(), Rc::new(types::RustTypeRenderer { derive_serde: true, derive_debug: true, + all_fields_optional: false, }), ); // remove the root type which we don't want to generate types for @@ -175,7 +175,6 @@ fn gen_mod_rs(config: &MdkRustGenConfig, tg: &Typegraph) -> anyhow::Result anyhow::Result anyhow::Result<()> { +fn e2e() -> anyhow::Result<()> { use crate::tests::*; let tg_name = "gen-test"; @@ -334,7 +333,7 @@ fn mdk_rs_e2e() -> anyhow::Result<()> { build_fn: |args| { Box::pin(async move { let status = tokio::process::Command::new("cargo") - .args("build --target wasm32-wasi".split(' ').collect::>()) + .args("clippy --target wasm32-wasi".split(' ').collect::>()) .current_dir(args.path) .kill_on_drop(true) .spawn()? @@ -346,7 +345,7 @@ fn mdk_rs_e2e() -> anyhow::Result<()> { Ok(()) }) }, - target_dir: "./tests/mat_rust/".into(), + target_dir: Some("./fixtures/mat_rust/".into()), }]) .await })?; diff --git a/libs/metagen/src/mdk_rust/static/mdk.rs b/libs/metagen/src/mdk_rust/static/mdk.rs index 814fa29a26..8a22d7ea99 100644 --- a/libs/metagen/src/mdk_rust/static/mdk.rs +++ b/libs/metagen/src/mdk_rust/static/mdk.rs @@ -1,6 +1,6 @@ // no-auto-license-header | @generated (pre-commit doesn't support two headers) // gen-start -#![allow(unused)] +#![allow(dead_code)] pub mod wit { wit_bindgen::generate!({ diff --git a/libs/metagen/src/mdk_rust/stubs.rs b/libs/metagen/src/mdk_rust/stubs.rs index 5c506f2acc..f73416b028 100644 --- a/libs/metagen/src/mdk_rust/stubs.rs +++ b/libs/metagen/src/mdk_rust/stubs.rs @@ -3,7 +3,7 @@ use super::utils::normalize_type_title; use crate::interlude::*; -use crate::mdk::*; +use crate::shared::*; use crate::utils::*; use std::fmt::Write; @@ -12,7 +12,7 @@ pub struct GenStubOptions {} pub fn gen_stub( fun: &StubbedFunction, mod_stub_traits: &mut GenDestBuf, - type_names: &HashMap>, + type_names: &BTreeMap>, _opts: &GenStubOptions, ) -> anyhow::Result { let TypeNode::Function { base, data } = &fun.node else { @@ -55,7 +55,7 @@ pub fn gen_stub( } pub fn gen_op_to_mat_map( - op_to_trait_map: &HashMap, + op_to_trait_map: &BTreeMap, dest: &mut GenDestBuf, _opts: &GenStubOptions, ) -> anyhow::Result<()> { @@ -176,10 +176,8 @@ mod test { r#"// gen-static-end use types::*; pub mod types { - use super::*; pub type MyInt = i64; } -use stubs::*; pub mod stubs { use super::*; pub trait MyFunc: Sized + 'static { diff --git a/libs/metagen/src/mdk_rust/types.rs b/libs/metagen/src/mdk_rust/types.rs index adf1c9b98c..37191dd54e 100644 --- a/libs/metagen/src/mdk_rust/types.rs +++ b/libs/metagen/src/mdk_rust/types.rs @@ -3,7 +3,7 @@ use super::utils::*; use crate::interlude::*; -use crate::mdk::types::*; +use crate::shared::types::*; use common::typegraph::*; use heck::ToPascalCase; use std::fmt::Write; @@ -11,6 +11,10 @@ use std::fmt::Write; pub struct RustTypeRenderer { pub derive_debug: bool, pub derive_serde: bool, + // this is used by the client since + // users might exclude fields on return + // types + pub all_fields_optional: bool, } impl RustTypeRenderer { @@ -81,21 +85,42 @@ impl RustTypeRenderer { } } impl RenderType for RustTypeRenderer { - fn render_name(&self, _renderer: &mut TypeRenderer, cursor: &VisitCursor) -> RenderedName { - use RenderedName::*; - let body_required = self.type_body_required(cursor.node.clone()); - match cursor.node.clone().deref() { - // functions will be absent in our generated types - TypeNode::Function { .. } => Name("()".to_string()), + fn render( + &self, + renderer: &mut TypeRenderer, + cursor: &mut VisitCursor, + ) -> anyhow::Result { + let body_required = type_body_required(cursor.node.clone()); + let name = match cursor.node.clone().deref() { + TypeNode::Function { .. } => "()".into(), + // if [type_body_required] says so, we usually need to generate + // aliases for even simple primitie types + TypeNode::Boolean { base, .. } if body_required => { + let ty_name = normalize_type_title(&base.title); + self.render_alias(renderer, &ty_name, "bool")?; + ty_name + } // under certain conditionds, we don't want to generate aliases // for primitive types. this includes // - types with defualt generated names // - types with no special semantics - TypeNode::Boolean { .. } if !body_required => Name("bool".to_string()), - TypeNode::Integer { .. } if !body_required => Name("i64".to_string()), - TypeNode::Float { .. } if !body_required => Name("f64".to_string()), - TypeNode::String { .. } if !body_required => Name("String".to_string()), + TypeNode::Boolean { .. } => "bool".into(), + + TypeNode::Float { base, .. } if body_required => { + let ty_name = normalize_type_title(&base.title); + self.render_alias(renderer, &ty_name, "f64")?; + ty_name + } + TypeNode::Float { .. } => "f64".into(), + + TypeNode::Integer { base, .. } if body_required => { + let ty_name = normalize_type_title(&base.title); + self.render_alias(renderer, &ty_name, "i64")?; + ty_name + } + TypeNode::Integer { .. } => "i64".into(), + TypeNode::String { data: StringTypeData { @@ -111,81 +136,32 @@ impl RenderType for RustTypeRenderer { .. }, } if title.starts_with("string_") => { - Name(normalize_type_title(&format!("string_{format}"))) - } - TypeNode::File { .. } if !body_required => Name("Vec".to_string()), - TypeNode::Optional { - // NOTE: keep this condition - // in sync with similar one - // below - base, - data: - OptionalTypeData { - default_value: None, - .. - }, - } if base.title.starts_with("optional_") => { - // since the type name of Optionl | Vec depends on - // the name of the inner type, we use placeholders at this ploint - // as cycles are dealt with later - Placeholder - } - TypeNode::List { - // NOTE: keep this condition - // in sync with similar one - // below - base, - data: - ListTypeData { - min_items: None, - max_items: None, - .. - }, - } if base.title.starts_with("list_") => { - // since the type name of Optionl | Vec depends on - // the name of the inner type, we use placeholders at this point - // as cycles are dealt with later - Placeholder - } - ty => Name(normalize_type_title(&ty.base().title)), - /* - TypeNode::Union { base, .. } => { - format!("{}Union", normalize_type_title(&base.title)) + let ty_name = normalize_type_title(&format!("string_{format}_{}", cursor.id)); + self.render_alias(renderer, &ty_name, "String")?; + ty_name } - TypeNode::Either { base, .. } => { - format!("{}Either", normalize_type_title(&base.title)) + TypeNode::String { base, .. } if body_required => { + let ty_name = normalize_type_title(&base.title); + self.render_alias(renderer, &ty_name, "String")?; + ty_name } - */ - } - } + TypeNode::String { .. } => "String".into(), - fn render_body( - &self, - renderer: &mut TypeRenderer, - ty_name: &str, - cursor: &mut VisitCursor, - ) -> anyhow::Result<()> { - match cursor.node.clone().deref() { - TypeNode::Function { .. } => {} - TypeNode::Boolean { .. } => { - self.render_alias(renderer, ty_name, "bool")?; - } - TypeNode::Float { .. } => { - self.render_alias(renderer, ty_name, "f64")?; - } - TypeNode::Integer { .. } => { - self.render_alias(renderer, ty_name, "i64")?; + TypeNode::File { base, .. } if body_required => { + let ty_name = normalize_type_title(&base.title); + self.render_alias(renderer, &ty_name, "Vec")?; + ty_name } - TypeNode::String { .. } => { - self.render_alias(renderer, ty_name, "String")?; - } - TypeNode::File { .. } => { - self.render_alias(renderer, ty_name, "Vec")?; - } - TypeNode::Any { .. } => { - self.render_alias(renderer, ty_name, "serde_json::Value")?; + TypeNode::File { .. } => "Vec".into(), + + TypeNode::Any { base, .. } if body_required => { + let ty_name = normalize_type_title(&base.title); + self.render_alias(renderer, &ty_name, "serde_json::Value")?; + ty_name } - TypeNode::Object { data, .. } => { + TypeNode::Any { .. } => "serde_json::Value".into(), + + TypeNode::Object { data, base } => { let props = data .properties .iter() @@ -193,10 +169,21 @@ impl RenderType for RustTypeRenderer { .map(|(name, &dep_id)| { let (ty_name, cyclic) = renderer.render_subgraph(dep_id, cursor)?; + let ty_name = match ty_name { + RenderedName::Name(name) => name, + RenderedName::Placeholder(name) => name, + }; + + let ty_name = match renderer.nodes[dep_id as usize].deref() { + TypeNode::Optional { .. } => ty_name.to_string(), + _ if !self.all_fields_optional => ty_name.to_string(), + _ => format!("Option<{ty_name}>"), + }; + let ty_name = if let Some(true) = cyclic { format!("Box<{ty_name}>") } else { - ty_name.to_string() + ty_name }; let normalized_prop_name = normalize_struct_prop_name(name); @@ -208,21 +195,40 @@ impl RenderType for RustTypeRenderer { Ok::<_, anyhow::Error>((normalized_prop_name, (ty_name, rename_name))) }) .collect::, _>>()?; - self.render_struct(renderer, ty_name, props)?; + + let ty_name = normalize_type_title(&base.title); + let ty_name = if self.all_fields_optional { + format!("{ty_name}Partial") + } else { + ty_name + }; + self.render_struct(renderer, &ty_name, props)?; + ty_name } TypeNode::Union { data: UnionTypeData { any_of: variants }, - .. + base, } | TypeNode::Either { data: EitherTypeData { one_of: variants }, - .. + base, } => { let variants = variants .iter() .map(|&inner| { let (ty_name, cyclic) = renderer.render_subgraph(inner, cursor)?; - let variant_name = ty_name.to_pascal_case(); + let (variant_name, ty_name) = match ty_name { + RenderedName::Name(name) => (name.to_pascal_case(), name), + RenderedName::Placeholder(name) => ( + renderer + .placeholder_string( + inner, + Box::new(|final_name| final_name.to_pascal_case()), + ) + .to_string(), + name, + ), + }; let ty_name = if let Some(true) = cyclic { format!("Box<{ty_name}>") } else { @@ -231,11 +237,12 @@ impl RenderType for RustTypeRenderer { Ok::<_, anyhow::Error>((variant_name, ty_name)) }) .collect::, _>>()?; - self.render_enum(renderer, ty_name, variants)?; + let ty_name = normalize_type_title(&base.title); + self.render_enum(renderer, &ty_name, variants)?; + ty_name } + // Simple optionals don't require aliases TypeNode::Optional { - // NOTE: keep this condition - // in sync with similar one above base, data: OptionalTypeData { @@ -245,33 +252,35 @@ impl RenderType for RustTypeRenderer { } if base.title.starts_with("optional_") => { // TODO: handle cyclic case where entire cycle is aliases let (inner_ty_name, cyclic) = renderer.render_subgraph(*item, cursor)?; + let inner_ty_name = match inner_ty_name { + RenderedName::Name(name) => name, + RenderedName::Placeholder(name) => name, + }; let inner_ty_name = if let Some(true) = cyclic { format!("Box<{inner_ty_name}>") } else { inner_ty_name.to_string() }; - let true_ty_name = format!("Option<{inner_ty_name}>"); - let true_ty_name: Rc = true_ty_name.into(); - let normalized_true_name = normalize_struct_prop_name(&true_ty_name); - renderer.replace_placeholder_ty_name( - cursor.id, - true_ty_name, - vec![(normalize_struct_prop_name(ty_name), normalized_true_name)], - ); + format!("Option<{inner_ty_name}>") } - TypeNode::Optional { data, .. } => { + TypeNode::Optional { data, base } => { // TODO: handle cyclic case where entire cycle is aliases let (inner_ty_name, cyclic) = renderer.render_subgraph(data.item, cursor)?; + let inner_ty_name = match inner_ty_name { + RenderedName::Name(name) => name, + RenderedName::Placeholder(name) => name, + }; let inner_ty_name = if let Some(true) = cyclic { format!("Box<{inner_ty_name}>") } else { inner_ty_name.to_string() }; - self.render_alias(renderer, ty_name, &format!("Option<{inner_ty_name}>"))?; + let ty_name = normalize_type_title(&base.title); + self.render_alias(renderer, &ty_name, &format!("Option<{inner_ty_name}>"))?; + ty_name } + // simple list types don't require aliases TypeNode::List { - // NOTE: keep this condition - // in sync with similar one above base, data: ListTypeData { @@ -283,36 +292,39 @@ impl RenderType for RustTypeRenderer { } if base.title.starts_with("list_") => { // TODO: handle cyclic case where entire cycle is aliases let (inner_ty_name, _) = renderer.render_subgraph(*items, cursor)?; - let true_ty_name = if let Some(true) = unique_items { + let inner_ty_name = match inner_ty_name { + RenderedName::Name(name) => name, + RenderedName::Placeholder(name) => name, + }; + if let Some(true) = unique_items { format!("std::collections::HashSet<{inner_ty_name}>") } else { format!("Vec<{inner_ty_name}>") - }; - let true_ty_name: Rc = true_ty_name.into(); - let normalized_true_name = normalize_struct_prop_name(&true_ty_name); - renderer.replace_placeholder_ty_name( - cursor.id, - true_ty_name, - vec![(normalize_struct_prop_name(ty_name), normalized_true_name)], - ); + } } - TypeNode::List { data, .. } => { + TypeNode::List { data, base } => { // TODO: handle cyclic case where entire cycle is aliases let (inner_ty_name, _) = renderer.render_subgraph(data.items, cursor)?; + let inner_ty_name = match inner_ty_name { + RenderedName::Name(name) => name, + RenderedName::Placeholder(name) => name, + }; + let ty_name = normalize_type_title(&base.title); if let Some(true) = data.unique_items { // let ty_name = format!("{inner_ty_name}Set"); self.render_alias( renderer, - ty_name, + &ty_name, &format!("std::collections::HashSet<{inner_ty_name}>"), )?; } else { // let ty_name = format!("{inner_ty_name}List"); - self.render_alias(renderer, ty_name, &format!("Vec<{inner_ty_name}>"))?; + self.render_alias(renderer, &ty_name, &format!("Vec<{inner_ty_name}>"))?; }; + ty_name } }; - Ok(()) + Ok(name) } } @@ -740,10 +752,11 @@ pub enum CEither { ]; for (test_name, nodes, name, test_out) in cases { let mut renderer = TypeRenderer::new( - &nodes, + nodes.iter().cloned().map(Rc::new).collect::>(), Rc::new(RustTypeRenderer { derive_serde: true, derive_debug: true, + all_fields_optional: false, }), ); let gen_name = renderer.render(nodes.len() as u32 - 1)?; diff --git a/libs/metagen/src/mdk_typescript/mod.rs b/libs/metagen/src/mdk_typescript/mod.rs index c8beb3143c..5702624103 100644 --- a/libs/metagen/src/mdk_typescript/mod.rs +++ b/libs/metagen/src/mdk_typescript/mod.rs @@ -1,19 +1,19 @@ // Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0. // SPDX-License-Identifier: MPL-2.0 -mod types; -mod utils; +pub mod types; +pub mod utils; use core::fmt::Write; use crate::interlude::*; -use crate::mdk::*; +use crate::shared::*; use crate::*; use crate::utils::GenDestBuf; -use self::mdk::types::NameMemo; -use self::mdk::types::TypeRenderer; +use self::shared::types::NameMemo; +use self::shared::types::TypeRenderer; #[derive(Serialize, Deserialize, Debug, garde::Validate)] pub struct MdkTypescriptGenConfig { @@ -147,7 +147,10 @@ fn gen_static(dest: &mut GenDestBuf) -> core::fmt::Result { } fn render_types(dest: &mut GenDestBuf, tg: &Typegraph) -> anyhow::Result { - let mut renderer = TypeRenderer::new(&tg.types, Rc::new(types::TypescriptTypeRenderer {})); + let mut renderer = TypeRenderer::new( + tg.types.iter().cloned().map(Rc::new).collect::>(), + Rc::new(types::TypescriptTypeRenderer {}), + ); // remove the root type which we don't want to generate types for // TODO: gql types || function wrappers for exposed functions // skip object 0, the root object where the `exposed` items are locted @@ -160,7 +163,7 @@ fn render_types(dest: &mut GenDestBuf, tg: &Typegraph) -> anyhow::Result anyhow::Result<()> { +fn e2e() -> anyhow::Result<()> { use crate::tests::*; let tg_name = "gen-test"; @@ -222,7 +225,7 @@ fn mdk_rs_e2e() -> anyhow::Result<()> { Ok(()) }) }, - target_dir: "./tests/mat_rust/".into(), + target_dir: None, }]) .await })?; diff --git a/libs/metagen/src/mdk_typescript/types.rs b/libs/metagen/src/mdk_typescript/types.rs index e7e27b2608..6fc6476c70 100644 --- a/libs/metagen/src/mdk_typescript/types.rs +++ b/libs/metagen/src/mdk_typescript/types.rs @@ -6,7 +6,7 @@ use std::fmt::Write; use common::typegraph::*; use super::utils::{normalize_struct_prop_name, normalize_type_title}; -use crate::{interlude::*, mdk::types::*}; +use crate::{interlude::*, shared::types::*}; pub struct TypescriptTypeRenderer {} impl TypescriptTypeRenderer { @@ -24,11 +24,15 @@ impl TypescriptTypeRenderer { &self, dest: &mut impl Write, ty_name: &str, - props: IndexMap>, + props: IndexMap, bool)>, ) -> std::fmt::Result { writeln!(dest, "export type {ty_name} = {{")?; - for (name, ty_name) in props.into_iter() { - writeln!(dest, " {name}: {ty_name};")?; + for (name, (ty_name, optional)) in props.into_iter() { + if optional { + writeln!(dest, " {name}?: {ty_name};")?; + } else { + writeln!(dest, " {name}: {ty_name};")?; + } } writeln!(dest, "}};")?; Ok(()) @@ -50,20 +54,46 @@ impl TypescriptTypeRenderer { } impl RenderType for TypescriptTypeRenderer { - fn render_name(&self, _renderer: &mut TypeRenderer, cursor: &VisitCursor) -> RenderedName { - use RenderedName::*; - let body_required = self.type_body_required(cursor.node.clone()); - match cursor.node.clone().deref() { - // functions will be absent in our gnerated types - TypeNode::Function { .. } => Name("void".into()), - // under certain conditionds, we don't want to generate aliases - // for primitive types. this includes - // - types with default generated names - // - types with no special semantics - TypeNode::Boolean { .. } if !body_required => Name("boolean".into()), - TypeNode::Integer { .. } if !body_required => Name("number".to_string()), - TypeNode::Float { .. } if !body_required => Name("number".to_string()), - TypeNode::String { .. } if !body_required => Name("string".to_string()), + fn render( + &self, + renderer: &mut TypeRenderer, + cursor: &mut VisitCursor, + ) -> anyhow::Result { + let body_required = type_body_required(cursor.node.clone()); + let name = match cursor.node.clone().deref() { + TypeNode::Function { .. } => "void".into(), + TypeNode::Boolean { base } if body_required => { + let ty_name = normalize_type_title(&base.title); + self.render_alias(renderer, &ty_name, "boolean")?; + ty_name + } + TypeNode::Boolean { .. } => "boolean".into(), + TypeNode::Float { base, .. } if body_required => { + let ty_name = normalize_type_title(&base.title); + self.render_alias(renderer, &ty_name, "number")?; + ty_name + } + TypeNode::Float { .. } => "number".into(), + TypeNode::Integer { base, .. } if body_required => { + let ty_name = normalize_type_title(&base.title); + self.render_alias(renderer, &ty_name, "number")?; + ty_name + } + TypeNode::Integer { .. } => "number".into(), + TypeNode::String { + base: + TypeNodeBase { + enumeration: Some(variants), + title, + .. + }, + .. + } if body_required => { + let ty_name = normalize_type_title(title); + // variants are valid strings in JSON (validated by the validator) + self.render_alias(renderer, &ty_name, &variants.join(" | "))?; + ty_name + } TypeNode::String { data: StringTypeData { @@ -79,109 +109,80 @@ impl RenderType for TypescriptTypeRenderer { .. }, } if title.starts_with("string_") => { - Name(normalize_type_title(&format!("string_{format}"))) - } - TypeNode::File { .. } if !body_required => Name("File".to_string()), - TypeNode::Optional { - // NOTE: keep this condition - // in sync with similar one - // below - base, - data: - OptionalTypeData { - default_value: None, - .. - }, - } if base.title.starts_with("optional_") => { - // since the type name of (T | undefined | null) and Array depends on - // the name of the inner type, we use placeholders at this ploint - Placeholder - } - TypeNode::List { - // NOTE: keep this condition - // in sync with similar one - // below - base, - data: - ListTypeData { - min_items: None, - max_items: None, - .. - }, - } if base.title.starts_with("list_") => { - // since the type name of (T | undefined | null) and Array depends on - // the name of the inner type, we use placeholders at this ploint - Placeholder + let ty_name = normalize_type_title(&format!("string_{format}_{}", cursor.id)); + self.render_alias(renderer, &ty_name, "string")?; + ty_name } - ty => Name(normalize_type_title(&ty.base().title)), - } - } - - fn render_body( - &self, - renderer: &mut TypeRenderer, - ty_name: &str, - cursor: &mut VisitCursor, - ) -> anyhow::Result<()> { - match cursor.node.clone().deref() { - TypeNode::Function { .. } => {} - TypeNode::Boolean { .. } => { - self.render_alias(renderer, ty_name, "boolean")?; + TypeNode::String { base, .. } if body_required => { + let ty_name = normalize_type_title(&base.title); + self.render_alias(renderer, &ty_name, "string")?; + ty_name } - TypeNode::Float { .. } => { - self.render_alias(renderer, ty_name, "number")?; + TypeNode::String { .. } => "string".into(), + TypeNode::File { base, .. } if body_required => { + let ty_name = normalize_type_title(&base.title); + self.render_alias(renderer, &ty_name, "File")?; + ty_name } - TypeNode::Integer { .. } => { - self.render_alias(renderer, ty_name, "number")?; + TypeNode::File { .. } => "File".into(), + TypeNode::Any { base } if body_required => { + let ty_name = normalize_type_title(&base.title); + self.render_alias(renderer, &ty_name, "any")?; + ty_name } - TypeNode::String { - base: - TypeNodeBase { - enumeration: Some(variants), - .. - }, - .. - } => { - // variants are valid strings in JSON (validated by the validator) - self.render_alias(renderer, ty_name, &variants.join(" | "))?; - } - TypeNode::String { .. } => { - self.render_alias(renderer, ty_name, "string")?; - } - TypeNode::File { .. } => { - self.render_alias(renderer, ty_name, "File")?; - } - TypeNode::Any { .. } => { - self.render_alias(renderer, ty_name, "any")?; - } - TypeNode::Object { data, .. } => { + TypeNode::Any { .. } => "any".into(), + TypeNode::Object { data, base } => { let props = data .properties .iter() // generate property types first .map(|(name, &dep_id)| { let (ty_name, _cyclic) = renderer.render_subgraph(dep_id, cursor)?; - Ok::<_, anyhow::Error>((normalize_struct_prop_name(&name[..]), ty_name)) + let ty_name = match ty_name { + RenderedName::Name(name) => name, + RenderedName::Placeholder(name) => name, + }; + let optional = matches!( + renderer.nodes[dep_id as usize].deref(), + TypeNode::Optional { .. } + ); + Ok::<_, anyhow::Error>(( + normalize_struct_prop_name(&name[..]), + (ty_name, optional), + )) }) .collect::, _>>()?; - self.render_object_type(renderer, ty_name, props)?; + + let ty_name = normalize_type_title(&base.title); + if !props.is_empty() { + self.render_object_type(renderer, &ty_name, props)?; + } else { + self.render_alias(renderer, &ty_name, "Record")?; + } + ty_name } TypeNode::Either { data: EitherTypeData { one_of: variants }, - .. + base, } | TypeNode::Union { data: UnionTypeData { any_of: variants }, - .. + base, } => { let variants = variants .iter() .map(|&inner| { let (ty_name, _cyclic) = renderer.render_subgraph(inner, cursor)?; + let ty_name = match ty_name { + RenderedName::Name(name) => name, + RenderedName::Placeholder(name) => name, + }; Ok::<_, anyhow::Error>(ty_name) }) .collect::, _>>()?; - self.render_union_type(renderer, ty_name, variants)?; + let ty_name = normalize_type_title(&base.title); + self.render_union_type(renderer, &ty_name, variants)?; + ty_name } TypeNode::Optional { // NOTE: keep this condition @@ -195,24 +196,26 @@ impl RenderType for TypescriptTypeRenderer { } if base.title.starts_with("optional_") => { // TODO: handle cyclic case where entire cycle is aliases let (inner_ty_name, _) = renderer.render_subgraph(*item, cursor)?; - - let true_ty_name = format!("({inner_ty_name}) | null | undefined"); - let true_ty_name: Rc = true_ty_name.into(); - let normalized_true_name = normalize_struct_prop_name(&true_ty_name); - renderer.replace_placeholder_ty_name( - cursor.id, - true_ty_name, - vec![(normalize_struct_prop_name(ty_name), normalized_true_name)], - ); + let inner_ty_name = match inner_ty_name { + RenderedName::Name(name) => name, + RenderedName::Placeholder(name) => name, + }; + format!("({inner_ty_name}) | null | undefined") } - TypeNode::Optional { data, .. } => { + TypeNode::Optional { data, base } => { // TODO: handle cyclic case where entire cycle is aliases let (inner_ty_name, _) = renderer.render_subgraph(data.item, cursor)?; + let inner_ty_name = match inner_ty_name { + RenderedName::Name(name) => name, + RenderedName::Placeholder(name) => name, + }; + let ty_name = normalize_type_title(&base.title); self.render_alias( renderer, - ty_name, + &ty_name, &format!("{inner_ty_name} | null | undefined"), )?; + ty_name } TypeNode::List { // NOTE: keep this condition @@ -228,31 +231,34 @@ impl RenderType for TypescriptTypeRenderer { } if base.title.starts_with("list_") => { // TODO: handle cyclic case where entire cycle is aliases let (inner_ty_name, _) = renderer.render_subgraph(*items, cursor)?; - let true_ty_name = if let Some(true) = unique_items { + let inner_ty_name = match inner_ty_name { + RenderedName::Name(name) => name, + RenderedName::Placeholder(name) => name, + }; + if let Some(true) = unique_items { // TODO: use sets? format!("Array<{inner_ty_name}>") } else { format!("Array<{inner_ty_name}>") - }; - let true_ty_name: Rc = true_ty_name.into(); - let normalized_true_name = normalize_struct_prop_name(&true_ty_name); - renderer.replace_placeholder_ty_name( - cursor.id, - true_ty_name, - vec![(normalize_struct_prop_name(ty_name), normalized_true_name)], - ); + } } - TypeNode::List { data, .. } => { + TypeNode::List { data, base } => { // TODO: handle cyclic case where entire cycle is aliases let (inner_ty_name, _) = renderer.render_subgraph(data.items, cursor)?; + let inner_ty_name = match inner_ty_name { + RenderedName::Name(name) => name, + RenderedName::Placeholder(name) => name, + }; + let ty_name = normalize_type_title(&base.title); if let Some(true) = data.unique_items { // FIXME: use set? - self.render_alias(renderer, ty_name, &format!("Array<{inner_ty_name}>"))?; + self.render_alias(renderer, &ty_name, &format!("Array<{inner_ty_name}>"))?; } else { - self.render_alias(renderer, ty_name, &format!("Array<{inner_ty_name}>"))?; + self.render_alias(renderer, &ty_name, &format!("Array<{inner_ty_name}>"))?; }; + ty_name } }; - anyhow::Ok(()) + Ok(name) } } diff --git a/libs/metagen/src/shared/client.rs b/libs/metagen/src/shared/client.rs new file mode 100644 index 0000000000..f11934f89d --- /dev/null +++ b/libs/metagen/src/shared/client.rs @@ -0,0 +1,168 @@ +// Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0. +// SPDX-License-Identifier: MPL-2.0 + +use crate::interlude::*; + +use super::types::*; +use common::typegraph::{EffectType, ListTypeData, OptionalTypeData}; + +pub struct RenderManifest { + pub return_types: HashSet, + pub arg_types: HashSet, + pub node_metas: HashSet, + pub selections: HashSet, + pub root_fns: Vec, +} + +pub struct RootFn { + pub id: u32, + pub name: String, + pub in_id: Option, + pub out_id: u32, + pub effect: EffectType, + pub select_ty: Option, +} + +/// Collect upfront all the items we need to render +pub fn get_manifest(tg: &Typegraph) -> Result { + let mut root_fns = vec![]; + let mut selections = HashSet::new(); + let mut return_types = HashSet::new(); + let mut node_metas = HashSet::new(); + let mut arg_types = HashSet::new(); + + let (_root_base, root) = tg.root().map_err(anyhow_to_eyre!())?; + for (key, &func_id) in &root.properties { + let TypeNode::Function { data, .. } = &tg.types[func_id as usize] else { + bail!( + "invalid typegraph: node of type {} instead of a root function", + tg.types[func_id as usize].type_name() + ); + }; + let mat = &tg.materializers[data.materializer as usize]; + + node_metas.insert(func_id); + return_types.insert(data.output); + root_fns.push(RootFn { + id: func_id, + name: key.clone(), + effect: mat.effect.effect.unwrap_or(EffectType::Read), + out_id: data.output, + // empty struct arguments don't need arguments + in_id: if matches!( + &tg.types[data.input as usize], + TypeNode::Object { data, .. } if !data.properties.is_empty() + ) { + arg_types.insert(data.input); + Some(data.input) + } else { + None + }, + // scalar return types don't need selections + select_ty: if super::is_composite(&tg.types, data.output) { + node_metas.insert(func_id); + selections.insert(func_id); + Some(data.output) + } else { + None + }, + }); + } + + for node in &tg.types { + if let TypeNode::Function { data, .. } = node { + if matches!( + &tg.types[data.input as usize], + TypeNode::Object { data, .. } if !data.properties.is_empty() + ) { + arg_types.insert(data.input); + } + } + } + + Ok(RenderManifest { + root_fns, + selections, + return_types, + node_metas, + arg_types, + }) +} + +pub enum SelectionTy { + Scalar, + ScalarArgs { arg_ty: Rc }, + Composite { select_ty: Rc }, + CompositeArgs { arg_ty: Rc, select_ty: Rc }, +} + +pub fn selection_for_field( + ty: u32, + arg_ty_names: &NameMemo, + renderer: &mut TypeRenderer, + cursor: &mut VisitCursor, +) -> Result { + let node = renderer.nodes[ty as usize].clone(); + Ok(match &node.deref() { + TypeNode::Boolean { .. } + | TypeNode::Float { .. } + | TypeNode::Integer { .. } + | TypeNode::String { .. } + | TypeNode::File { .. } => SelectionTy::Scalar, + TypeNode::Function { data, .. } => { + let arg_ty = if !matches!( + renderer.nodes[data.input as usize].deref(), + TypeNode::Object { data, .. } if data.properties.is_empty() + ) { + Some(arg_ty_names.get(&data.input).unwrap().clone()) + } else { + None + }; + match ( + arg_ty, + selection_for_field(data.output, arg_ty_names, renderer, cursor)?, + ) { + (None, SelectionTy::Scalar) => SelectionTy::Scalar, + (Some(arg_ty), SelectionTy::Scalar) => SelectionTy::ScalarArgs { arg_ty }, + (None, SelectionTy::Composite { select_ty }) => { + SelectionTy::Composite { select_ty } + } + (Some(arg_ty), SelectionTy::Composite { select_ty }) => { + SelectionTy::CompositeArgs { select_ty, arg_ty } + } + (_, SelectionTy::CompositeArgs { .. }) | (_, SelectionTy::ScalarArgs { .. }) => { + unreachable!("function can not return a function") + } + } + } + TypeNode::Optional { + data: OptionalTypeData { item, .. }, + .. + } + | TypeNode::List { + data: ListTypeData { items: item, .. }, + .. + } => selection_for_field(*item, arg_ty_names, renderer, cursor)?, + TypeNode::Object { .. } => SelectionTy::Composite { + select_ty: renderer.render_subgraph(ty, cursor)?.0.unwrap(), + }, + TypeNode::Either { + data: common::typegraph::EitherTypeData { one_of: variants }, + .. + } + | TypeNode::Union { + data: common::typegraph::UnionTypeData { any_of: variants }, + .. + } => { + let select_ty = renderer.render_subgraph(ty, cursor)?.0.unwrap(); + match selection_for_field(variants[0], arg_ty_names, renderer, cursor)? { + SelectionTy::Scalar => SelectionTy::Scalar, + SelectionTy::Composite { .. } => SelectionTy::Composite { select_ty }, + SelectionTy::CompositeArgs { .. } | SelectionTy::ScalarArgs { .. } => { + unreachable!("function can not be a union/either member") + } + } + } + TypeNode::Any { .. } => unimplemented!("Any type support not implemented"), + }) +} diff --git a/libs/metagen/src/mdk/mod.rs b/libs/metagen/src/shared/mod.rs similarity index 51% rename from libs/metagen/src/mdk/mod.rs rename to libs/metagen/src/shared/mod.rs index b40c82034b..b063417b55 100644 --- a/libs/metagen/src/mdk/mod.rs +++ b/libs/metagen/src/shared/mod.rs @@ -4,6 +4,7 @@ //! This module contains common logic for mdk generation //! imlementations +pub mod client; pub mod types; use common::typegraph::{runtimes::TGRuntime, Materializer}; @@ -62,3 +63,53 @@ pub fn filter_stubbed_funcs( .collect(); Ok(stubbed_funcs) } + +pub fn is_composite(types: &[TypeNode], id: u32) -> bool { + match &types[id as usize] { + TypeNode::Function { .. } => panic!("function type isn't composite or scalar"), + TypeNode::Any { .. } => panic!("unexpected Any type as output"), + TypeNode::Boolean { .. } + | TypeNode::Float { .. } + | TypeNode::Integer { .. } + | TypeNode::String { .. } + | TypeNode::File { .. } => false, + TypeNode::Object { .. } => true, + TypeNode::Optional { data, .. } => is_composite(types, data.item), + TypeNode::List { data, .. } => is_composite(types, data.items), + TypeNode::Union { data, .. } => data.any_of.iter().any(|&id| is_composite(types, id)), + TypeNode::Either { data, .. } => data.one_of.iter().any(|&id| is_composite(types, id)), + } +} + +pub fn get_gql_type(types: &[TypeNode], id: u32, optional: bool) -> String { + let name = match &types[id as usize] { + TypeNode::Optional { data, .. } => return get_gql_type(types, data.item, true), + TypeNode::List { data, .. } => format!("[{}]", get_gql_type(types, data.items, true)), + TypeNode::String { base, .. } => { + if base.as_id { + "ID".into() + } else { + "String".into() + } + } + TypeNode::Boolean { .. } => "Boolean".into(), + TypeNode::Float { .. } => "Float".into(), + TypeNode::Integer { .. } => "Int".into(), + node => node.base().title.clone(), + }; + if !optional { + format!("{name}!") + } else { + name + } +} +/* + getGraphQLType(typeNode: TypeNode, optional = false): string { + const scalarType = GRAPHQL_SCALAR_TYPES[typeNode.type]; + if (scalarType != null) { + return scalarType; + } + + return typeNode.title; + } +*/ diff --git a/libs/metagen/src/shared/types.rs b/libs/metagen/src/shared/types.rs new file mode 100644 index 0000000000..cc5736690a --- /dev/null +++ b/libs/metagen/src/shared/types.rs @@ -0,0 +1,321 @@ +// Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0. +// SPDX-License-Identifier: MPL-2.0 + +use std::fmt::Write; + +use common::typegraph::*; + +use crate::{interlude::*, utils::GenDestBuf}; + +pub type VisitedTypePaths = HashMap>>; + +#[derive(Debug, Clone)] +pub enum RenderedName { + Name(Rc), + Placeholder(Rc), +} + +impl RenderedName { + pub fn unwrap(self) -> Rc { + match self { + RenderedName::Name(name) => name, + RenderedName::Placeholder(name) => name, + } + } +} + +/// This type tracks the type graph traversal path. +pub struct VisitCursor { + pub id: u32, + pub node: Rc, + pub path: Vec, + pub visited_path: VisitedTypePaths, +} + +/// Trait to provides implementation for a lanaguage specific +/// type code generation +/// Used by [TypeRenderer]. +pub trait RenderType { + /// [TypeRenderer] implements [core::fmt::Write] and the implementation is expected + /// to write the generated code into it but it may choose not to do so. Types that refer + /// others can call [TypeRenderer.render_subgraph] to render other types first. + fn render( + &self, + renderer: &mut TypeRenderer, + visit_cursor: &mut VisitCursor, + ) -> anyhow::Result; + + /// Wether or not the parent node was visited when a part of the graph + /// was traversed in an _unfortunate_ manner. This is more than a cyclic + /// test but a cycle that's _unfortunate_. If the implementaiton is + /// resistant to certain types of cycles, it can return false to signal + /// that the cycle is fortunate and doesn't need special handling. + /// + /// The default implementation treats all cycles as unfortunate excepts + /// those that are broken by a List type. + fn is_path_unfortunately_cyclic( + &self, + renderer: &TypeRenderer, + parent_cursor: &VisitCursor, + current_cursor: &VisitCursor, + ) -> Option { + current_cursor + .visited_path + .get(&parent_cursor.id) + .map(|cyclic_paths| { + // for all cycles that lead back to current + cyclic_paths + .iter() + .map(|path| { + path[parent_cursor.path.len()..] + .iter() + // until we arrive at parent + .take_while(|&&dep_id| dep_id != parent_cursor.id) + // see if any are lists + .any(|&dep_id| { + matches!( + renderer.nodes[dep_id as usize].deref(), + TypeNode::List { .. } + ) + }) + }) + // we know this whole branch is unsized if + // any one of the paths don't contain a + // type stored on the heap i.e. a vec + .any(|has_list| !has_list) + }) + } +} + +pub type NameMemo = std::collections::BTreeMap>; + +/// type_id, old_str, (final_ty_name) -> new_str +type ReplacementRecords = Vec<(u32, Rc, Box String>)>; + +/// Helper for generating type bodies that's cycle aware. +pub struct TypeRenderer { + dest: GenDestBuf, + pub nodes: Vec>, + name_memo: HashMap, + render_type: Rc, + replacement_records: ReplacementRecords, +} + +impl TypeRenderer { + pub fn new(nodes: Vec>, render_type: Rc) -> Self { + Self { + dest: GenDestBuf { + buf: Default::default(), + }, + name_memo: Default::default(), + nodes, + render_type, + replacement_records: Default::default(), + } + } + pub fn is_composite(&self, id: u32) -> bool { + match self.nodes[id as usize].deref() { + TypeNode::Function { .. } => panic!("function type isn't composite or scalar"), + TypeNode::Any { .. } => panic!("Any tye isn't composite or scalar"), + TypeNode::Boolean { .. } + | TypeNode::Float { .. } + | TypeNode::Integer { .. } + | TypeNode::String { .. } + | TypeNode::File { .. } => false, + TypeNode::Object { .. } => true, + TypeNode::Optional { data, .. } => self.is_composite(data.item), + TypeNode::List { data, .. } => self.is_composite(data.items), + TypeNode::Union { data, .. } => data.any_of.iter().any(|&id| self.is_composite(id)), + TypeNode::Either { data, .. } => data.one_of.iter().any(|&id| self.is_composite(id)), + } + } + + pub fn placeholder_string( + &mut self, + target_id: u32, + replacement_maker: Box String>, + ) -> Rc { + // dbg!((&id, &replacement_records)); + let string: Rc = format!("&&placeholder{}%%", self.replacement_records.len()).into(); + self.replacement_records + .push((target_id, string.clone(), replacement_maker)); + string + } + + pub fn render(&mut self, id: u32) -> anyhow::Result> { + let (name, _) = self.render_subgraph( + id, + &mut VisitCursor { + id: u32::MAX, + node: self.nodes[0].clone(), + path: vec![], + visited_path: Default::default(), + }, + )?; + match name { + RenderedName::Name(name) => Ok(name), + RenderedName::Placeholder(_) => unreachable!(), + } + } + + /// The flag notifies if the subgraph was cyclic to the current type and + /// the implementaiton may correct accordingly. + pub fn render_subgraph( + &mut self, + id: u32, + parent_cursor: &mut VisitCursor, + ) -> anyhow::Result<(RenderedName, Option)> { + let my_path: Vec<_> = parent_cursor + .path + .iter() + .copied() + .chain(std::iter::once(id)) + .collect(); + + let node = self.nodes[id as usize].clone(); + + let mut current_cursor = VisitCursor { + id, + node: node.clone(), + visited_path: [(id, vec![my_path.clone()])].into_iter().collect(), + path: my_path, + }; + + // short circuit if we've already generated the type + let ty_name = if let Some(name) = self.name_memo.get(&id) { + name.clone() + } else if parent_cursor.path.contains(&id) { + let ancestor_placeholder = RenderedName::Placeholder( + self.placeholder_string(id, Box::new(|ty_name| ty_name.into())), + ); + self.name_memo.insert(id, ancestor_placeholder.clone()); + ancestor_placeholder + } else { + let render_type_impl = self.render_type.clone(); + + let ty_name = render_type_impl.render(self, &mut current_cursor)?; + let ty_name: Rc = ty_name.into(); + + // if let Some(RenderedName::Placeholder(placeholder)) = self.name_memo.get(&id) {} + + self.name_memo + .insert(id, RenderedName::Name(ty_name.clone())); + + RenderedName::Name(ty_name) + }; + + let cyclic = + self.render_type + .is_path_unfortunately_cyclic(self, parent_cursor, ¤t_cursor); + for (id, paths) in current_cursor.visited_path { + parent_cursor + .visited_path + .entry(id) + .or_default() + .extend(paths); + } + Ok((ty_name, cyclic)) + } + + pub fn finalize(self) -> (String, NameMemo) { + let mut out = self.dest.buf; + let name_memo = self + .name_memo + .into_iter() + .map(|(key, val)| { + let RenderedName::Name(val) = val else { + panic!("placeholder name found at finalize for {key}") + }; + (key, val) + }) + .collect::(); + for (id, from, fun) in self.replacement_records.into_iter().rev() { + // dbg!((&_id, &records)); + let Some(name) = name_memo.get(&id) else { + panic!("unable to find rendered name for replacement target {id}") + }; + let to = fun(name); + out = out.replace(&from[..], &to); + } + (out, name_memo) + } +} + +impl Write for TypeRenderer { + #[inline] + fn write_str(&mut self, s: &str) -> core::fmt::Result { + ::write_str(&mut self.dest, s) + } + + #[inline] + fn write_char(&mut self, c: char) -> core::fmt::Result { + ::write_char(&mut self.dest, c) + } +} + +/// Most languages don't need to generate bodies for all types +/// types and usually only need reference to the built-in primitives. +/// This function encodes that logic for the common case but won't +/// apply to all langauges. +/// +/// To be specific, it returns true for all but the simple primitive types. +/// It also returns true if the primitive has a user defined alias, +/// type validator and other interesting metadata. +pub fn type_body_required(node: Rc) -> bool { + match node.deref() { + // functions will be absent in our gnerated types + TypeNode::Function { .. } => false, + // under certain conditionds, we don't want to generate aliases + // for primitive types. this includes + // - types with default generated names + // - types with no special semantics + TypeNode::Boolean { base } if base.title.starts_with("boolean_") => false, + TypeNode::Integer { + base, + data: + common::typegraph::IntegerTypeData { + minimum: None, + maximum: None, + multiple_of: None, + exclusive_minimum: None, + exclusive_maximum: None, + }, + } if base.title.starts_with("integer_") => false, + TypeNode::Float { + base, + data: + FloatTypeData { + minimum: None, + maximum: None, + multiple_of: None, + exclusive_minimum: None, + exclusive_maximum: None, + }, + } if base.title.starts_with("float_") => false, + TypeNode::String { + base: + TypeNodeBase { + enumeration: None, + title, + .. + }, + data: + StringTypeData { + min_length: None, + max_length: None, + format: None, + pattern: None, + }, + } if title.starts_with("string_") => false, + TypeNode::File { + base, + data: + FileTypeData { + min_size: None, + max_size: None, + mime_types: None, + }, + } if base.title.starts_with("file_") => false, + _ => true, + } +} diff --git a/libs/metagen/src/tests/fixtures.rs b/libs/metagen/src/tests/fixtures.rs index 1743eee48e..76ab8019b6 100644 --- a/libs/metagen/src/tests/fixtures.rs +++ b/libs/metagen/src/tests/fixtures.rs @@ -7,7 +7,7 @@ use common::typegraph::*; pub async fn test_typegraph_1() -> anyhow::Result> { let out = tokio::process::Command::new("cargo") .args( - "run -p meta-cli -- serialize -f tests/tg.ts -vvv" + "run -p meta-cli -- serialize -f fixtures/tg.ts -vvv" // "run -p meta-cli -- serialize -f ../../examples/typegraphs/reduce.py" .split(' ') .collect::>(), @@ -105,3 +105,27 @@ pub fn default_type_node_base() -> TypeNodeBase { enumeration: None, } } + +pub async fn test_typegraph_3() -> anyhow::Result> { + let out = tokio::process::Command::new("cargo") + .args( + "run -p meta-cli -- serialize -f fixtures/tg2.ts -vvv" + // "run -p meta-cli -- serialize -f ../../examples/typegraphs/reduce.py" + .split(' ') + .collect::>(), + ) + .env( + "MCLI_LOADER_CMD", + "deno run -A --import-map=../../typegate/import_map.json {filepath}", + ) + .kill_on_drop(true) + .output() + .await?; + let mut tg: Vec> = serde_json::from_slice(&out.stdout).with_context(|| { + format!( + "error deserializing typegraph: {out:?}\nstderr):\n{}\n---END---", + std::str::from_utf8(&out.stderr).unwrap(), + ) + })?; + Ok(tg.pop().unwrap()) +} diff --git a/libs/metagen/src/tests/mod.rs b/libs/metagen/src/tests/mod.rs index f86667e56c..96f1702610 100644 --- a/libs/metagen/src/tests/mod.rs +++ b/libs/metagen/src/tests/mod.rs @@ -33,7 +33,7 @@ type BoxFuture = std::pin::Pin + Send pub struct E2eTestCase { pub target: String, pub config: config::Config, - pub target_dir: PathBuf, + pub target_dir: Option, pub typegraphs: HashMap>, pub build_fn: fn(BuildArgs) -> BoxFuture>, } @@ -44,11 +44,11 @@ pub async fn e2e_test(cases: Vec) -> anyhow::Result<()> { let tmp_dir = tokio::task::spawn_blocking(tempfile::tempdir) .await?? .into_path(); - { - let mut dir = tokio::fs::read_dir(&case.target_dir).await?; + if let Some(target_dir) = &case.target_dir { + let mut dir = tokio::fs::read_dir(target_dir).await?; while let Some(entry) = dir.next_entry().await? { let entry_path = entry.path(); - let target_path = tmp_dir.join(entry.path().strip_prefix(&case.target_dir)?); + let target_path = tmp_dir.join(entry.path().strip_prefix(target_dir)?); tokio::fs::copy(entry_path, &target_path) .await .context("error copying target_dir to temp dir")?; @@ -65,7 +65,9 @@ pub async fn e2e_test(cases: Vec) -> anyhow::Result<()> { for (path, buf) in files.0 { let path = tmp_dir.join(path); tokio::fs::create_dir_all(path.parent().unwrap()).await?; - tokio::fs::write(path, buf.contents).await?; + if buf.overwrite || !tokio::fs::try_exists(&path).await? { + tokio::fs::write(path, buf.contents).await?; + } } // compile (case.build_fn)(BuildArgs { @@ -80,7 +82,7 @@ pub async fn e2e_test(cases: Vec) -> anyhow::Result<()> { // TODO: query generated stub functions // cleanup - // tokio::fs::remove_dir_all(tmp_dir).await?; + tokio::fs::remove_dir_all(tmp_dir).await?; // node.try_undeploy(&typegraphs.keys().cloned().collect::>()).await?; } Ok(()) diff --git a/libs/metagen/tests/mat_rust/Cargo.toml b/libs/metagen/tests/mat_rust/Cargo.toml deleted file mode 100644 index 9f5caf23ce..0000000000 --- a/libs/metagen/tests/mat_rust/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "mat_rust" -edition = "2021" -version = "0.4.8" - -[workspace] -members = ["gen"] - -[lib] -path = "lib.rs" -crate-type = ["cdylib", "rlib"] - -[dependencies] -gen_test_mdk.path = "./gen" diff --git a/libs/metagen/tests/pnpm-lock.yaml b/libs/metagen/tests/pnpm-lock.yaml deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/meta-lsp/package.json b/meta-lsp/package.json index b837012915..b63bb83bb1 100644 --- a/meta-lsp/package.json +++ b/meta-lsp/package.json @@ -44,5 +44,6 @@ "typescript": "^5.3.2", "tsx": "4.7", "lcov": "1.16" - } + }, + "packageManager": "pnpm@9.5.0+sha1.8c155dc114e1689d18937974f6571e0ceee66f1d" } diff --git a/typegate/deno.lock b/typegate/deno.lock index 4802ad3981..40d42ce0bf 100644 --- a/typegate/deno.lock +++ b/typegate/deno.lock @@ -1363,46 +1363,6 @@ "https://deno.land/x/convert_bytes@v2.1.1/src/utility.ts": "a94b4c50286910a23a90c0a0510e8191fa3311dec44d062a6d4fe3d5b7ff8176", "https://deno.land/x/crc32@v0.2.0/mod.ts": "de7a3fa2d4ef24b96fc21e1cc4d2d65d1d2b1dcea92f63960e3e11bfa82df0fa", "https://deno.land/x/ctrlc@0.2.1/mod.ts": "b7895894c596f2e8355d3b181ed30fa74cb54b94c419b730f6cb6ff96b20ec8b", - "https://deno.land/x/dax@0.39.2/mod.ts": "309f96ce11fa8a1bb244fdfdcb60fa66f092200a72295402094aab88bdf02bdd", - "https://deno.land/x/dax@0.39.2/src/command.ts": "01980353fec193bbc7f1c3690bb87472607d0b1b7d9844d17904920b907cd0de", - "https://deno.land/x/dax@0.39.2/src/command_handler.ts": "a9e40f0f1ec57318e62904b5785fede82dcdf1101922ebb3ebfad8f1c4d9c8df", - "https://deno.land/x/dax@0.39.2/src/commands/args.ts": "a138aef24294e3cbf13cef08f4836d018e8dd99fd06ad82e7e7f08ef680bbc1d", - "https://deno.land/x/dax@0.39.2/src/commands/cat.ts": "ac42675faaf87609fd5d4bff9a7741cef7fd4465d6de709b8ccabe70f7fd4c98", - "https://deno.land/x/dax@0.39.2/src/commands/cd.ts": "32533465933fada680c1fc887e24b65c8d4de3f5f98d0f55950dcead64da445c", - "https://deno.land/x/dax@0.39.2/src/commands/cp_mv.ts": "29a11a0f6c81e9a19b897c9b3178594f06a55a00ca28cb63c17773e154b34299", - "https://deno.land/x/dax@0.39.2/src/commands/echo.ts": "4672ad44a39fdcb3f9d9296a7c5773895bfae0f50a617fd999d1f0ca62a6aa36", - "https://deno.land/x/dax@0.39.2/src/commands/exit.ts": "508a388671240381a2a9a8910d4bf068421ace27ae1cc10c056df48cf9d23334", - "https://deno.land/x/dax@0.39.2/src/commands/export.ts": "c10d1dc6a45fd00e40afa6b19d7ecd29d09333f422b5b0fc75863baf13350969", - "https://deno.land/x/dax@0.39.2/src/commands/mkdir.ts": "28220eae8d7e872dca26b8217ad1b7e15eacf24e92d80277201caacbbecb8232", - "https://deno.land/x/dax@0.39.2/src/commands/printenv.ts": "8e1210b01ef5fcb8de71623f1195aab6ee034dbe684cd08e068ab855f9133ff9", - "https://deno.land/x/dax@0.39.2/src/commands/pwd.ts": "113af521c5dc257310c26815295c691b8f894051249d1eb8ba49a46965e28c68", - "https://deno.land/x/dax@0.39.2/src/commands/rm.ts": "a3441ddf9cb8f07fdf1657cb475caefcae71b71378681f4948e8ada93ef4478d", - "https://deno.land/x/dax@0.39.2/src/commands/sleep.ts": "2ba65ab19ae0f78709070f43d588c8fd3ff76d0299d2485e587aaaed7b86dd51", - "https://deno.land/x/dax@0.39.2/src/commands/test.ts": "336008d64d9737474ee225be9f8bd6fbad3f1c2128ba427e1b42d2c98366554f", - "https://deno.land/x/dax@0.39.2/src/commands/touch.ts": "b8229c2c38b56ebff054dd8dc70109cb85bcef5aaf160193fd80ac93924aa7ad", - "https://deno.land/x/dax@0.39.2/src/commands/unset.ts": "a85e9aca0606fd582114c5fc1f97fd6838a766110bad72dce7bd386172c1fbbb", - "https://deno.land/x/dax@0.39.2/src/common.ts": "7a96f3e4d576f92be12364d520107cff54b3d1a98124b5a88538494c5109c6e6", - "https://deno.land/x/dax@0.39.2/src/console/confirm.ts": "d9128d10b77fcc0a8df2784f71c79df68f5c8e00a34b04547b9ba9ddf1c97f96", - "https://deno.land/x/dax@0.39.2/src/console/logger.ts": "e0ab5025915cef70df03681c756e211f25bb2e4331f82ed4256b17ddd9e794ea", - "https://deno.land/x/dax@0.39.2/src/console/mod.ts": "de8af7d646f6cb222eee6560171993690247941b13ed9d757789d16f019d73ee", - "https://deno.land/x/dax@0.39.2/src/console/multiSelect.ts": "31003744e58f45f720271bd034d8cfba1055c954ba02d77a2f2eb21e4c1ed55a", - "https://deno.land/x/dax@0.39.2/src/console/progress/format.ts": "15ddbb8051580f88ed499281e12ca6f881f875ab73268d7451d7113ee130bd7d", - "https://deno.land/x/dax@0.39.2/src/console/progress/interval.ts": "54ffc8c7501f8ab0b6370e120c00e9e2d3e9b0640fc2dc2989bbf22855172ed0", - "https://deno.land/x/dax@0.39.2/src/console/progress/mod.ts": "dd9330c3edd1790d70808d043f417f0eaf80a4442a945545c38e47ce11e907b6", - "https://deno.land/x/dax@0.39.2/src/console/prompt.ts": "1ad65c8a5a27fb58ce6138f8ebefe2fca4cd12015fea550fbdc62f875d4b31f7", - "https://deno.land/x/dax@0.39.2/src/console/select.ts": "c9d7124d975bf34d52ea1ac88fd610ed39db8ee6505b9bb53f371cef2f56c6ab", - "https://deno.land/x/dax@0.39.2/src/console/utils.ts": "6f2c4d7c98c13d40b0a16af622d654495eace06778343eb1507ecd0f3875d3ab", - "https://deno.land/x/dax@0.39.2/src/deps.ts": "c6f4195419244d38879d4dd14de5190a507818ac62a5a992493c7567a1235f7d", - "https://deno.land/x/dax@0.39.2/src/lib/mod.ts": "7d9d0cf99ecd3ff5c3e5329ea9e456b737581afb4a5c94c3f90244c990b5630d", - "https://deno.land/x/dax@0.39.2/src/lib/rs_lib.generated.js": "0a1a482c4387379106ef0da69534ebc5b0c2a1ec9f6dab76833fe84a7e6bbdf6", - "https://deno.land/x/dax@0.39.2/src/path.ts": "a1ca507225a516336f083432b992d83ad4cddeea9a91eef69abbbebb729721ae", - "https://deno.land/x/dax@0.39.2/src/pipes.ts": "5ed17dc0aea34d219568d59c826c04e319a91497cac2be7c51a99603a3771c70", - "https://deno.land/x/dax@0.39.2/src/request.ts": "21de624bc21155cf6143ea5eddf4de706944e26af10219957da4b8b20c7104ce", - "https://deno.land/x/dax@0.39.2/src/result.ts": "719a9b4bc6bafeec785106744381cd5f37927c973334fcba6a33b6418fb9e7be", - "https://deno.land/x/dax@0.39.2/src/runtimes/process.common.ts": "692afd5fa15f40ce452904fa5ce380462cee56fbee7abe271e509e78da997200", - "https://deno.land/x/dax@0.39.2/src/runtimes/process.deno.ts": "50f85a086a9208b26c10dc2c1406fd9274226401f670d6862fdee05433c9ea4e", - "https://deno.land/x/dax@0.39.2/src/shell.ts": "b95677094cc553c987f0f113a95a45d72f2896ef82754f9831029efbbafea5d5", - "https://deno.land/x/dax@0.39.2/src/vendor/outdent.ts": "4d0283726579688c50b20c4b779e068acd3fa159a8784a4549e5e21bbef0ae64", "https://deno.land/x/deep_eql@v5.0.1/index.js": "60e1547b99d4ae08df387067c2ac0a1b9ab42f212f0d8a11b8b0b61270d2b1c4", "https://deno.land/x/deno_cache@0.5.2/auth_tokens.ts": "5d1d56474c54a9d152e44d43ea17c2e6a398dd1e9682c69811a313567c01ee1e", "https://deno.land/x/deno_cache@0.5.2/cache.ts": "92ce8511e1e5c00fdf53a41619aa77d632ea8e0fc711324322e4d5ebf8133911", @@ -1534,7 +1494,6 @@ "https://deno.land/x/ts_morph@18.0.0/common/typescript.js": "d5c598b6a2db2202d0428fca5fd79fc9a301a71880831a805d778797d2413c59", "https://deno.land/x/wasmbuild@0.15.0/cache.ts": "89eea5f3ce6035a1164b3e655c95f21300498920575ade23161421f5b01967f4", "https://deno.land/x/wasmbuild@0.15.0/loader.ts": "d98d195a715f823151cbc8baa3f32127337628379a02d9eb2a3c5902dbccfc02", - "https://deno.land/x/which@0.3.0/mod.ts": "3e10d07953c14e4ddc809742a3447cef14202cdfe9be6678a1dfc8769c4487e6", "https://deno.land/x/xhr@0.3.0/mod.ts": "094aacd627fd9635cd942053bf8032b5223b909858fa9dc8ffa583752ff63b20", "https://deno.land/x/zod@v3.22.2/ZodError.ts": "4de18ff525e75a0315f2c12066b77b5c2ae18c7c15ef7df7e165d63536fdf2ea", "https://deno.land/x/zod@v3.22.2/errors.ts": "5285922d2be9700cc0c70c95e4858952b07ae193aa0224be3cbd5cd5567eabef", @@ -1681,80 +1640,80 @@ "https://raw.githubusercontent.com/levibostian/deno-udd/ignore-prerelease/registry.ts": "fd8e1b05f14cb988fee7a72a51e68131a920f7d4b72f949d9b86794b3c699671", "https://raw.githubusercontent.com/levibostian/deno-udd/ignore-prerelease/search.ts": "52f9a539ca76893c47d01f8c6d401487ea286d54d1305b079b8727598e4c847a", "https://raw.githubusercontent.com/levibostian/deno-udd/ignore-prerelease/semver.ts": "c051a906405dd72b55434eb0f390f678881379d57847abe4ec60d8a02af4f6f2", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/deps/cli.ts": "aac025f9372ad413b9c2663dc7f61affd597820d9448f010a510d541df3b56ea", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/deps/common.ts": "f775710b66a9099b98651cd3831906466e9b83ef98f2e5c080fd59ee801c28d4", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/deps/ports.ts": "3c60d1f7ab626ffdd81b37f4e83a780910936480da8fe24f4ccceaefa207d339", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/files/mod.ts": "a934ddb4803896e27b40644fe75623d584e01f30bbe1e16eb26bd772aa5b6064", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/host/types.ts": "f450d9b9c0eced2650262d02455aa6f794de0edd6b052aade256882148e5697f", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/install/mod.ts": "aa54eb3e119f28d33e61645c89669da292ee00376068ead8f45be2807e7a9989", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/install/utils.ts": "d4634d4fc0e963f540402b4ca7eb5dcba340eaa0d8fceb43af57d722ad267115", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/mod.ts": "1d31b4f801ae2ebad052d219236699c4f227b6ce53c6e5016deaed5fcd00dbb6", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/envs/inter.ts": "84805fa208754a08f185dca7a5236de3760bbc1d0df96af86ea5fd7778f827a2", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/envs/mod.ts": "5f37b9f155808f8d6d51e1f16f58c07914d8c7d8070bc5c2fb5076ab748798a7", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/envs/posix.ts": "09e410e3fea9c303a5148ff2a22697474320442b9fea0bd3fc932d6828fe820f", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/envs/reducer.ts": "50517084caaf73ce6618141ee4d97795060a0d3169651da7abd7251a3204465a", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/envs/types.ts": "ab9715cf02e9d73f553ae757db347863be23e1e9daf94d18aab716fc27b3dbc1", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/mod.ts": "fc1cb9176c6557b44ae9c6536fa51c6c4f80ac01fc476d15b0a217e70cb0d176", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/ports/ambient.ts": "823ec8d98702a60e6bfcdbeb64b69dc9f5039e73a1f10e87cd51210c1aaf52d5", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/ports/base.ts": "8ef8a8de372420bddcd63a1b363937f43d898059e99478a58621e8432bcd5891", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/ports/db.ts": "a309d1058f66079a481141c3f1733d928b9af8a37b7ce911b1228f70fd24df0f", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/ports/ghrel.ts": "ebbc30a5c31244131d937eadca73fbc099c9e7bdf0ad4f668766d4388ede143c", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/ports/inter.ts": "b3999e73d73d7f928a8de86e5e2261fe6b1450ceedfb54f24537bf0803532ed0", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/ports/mod.ts": "78db7040e724f84c95b1a0fdeaf0cfc53382482e8905cd352189756b953556cc", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/ports/reducers.ts": "d04e813652101f67f946242df68429ed5540e499fbdb7776b8be5703f16754c8", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/ports/std.ts": "985902519aafef6e8e6aecc8922e70abdea5b8e97d5439bf94338b93242fe11f", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/ports/sync.ts": "a7a297f6b098360d56af168692f3cff96f8ceeb5189e5baa249e094f8d9c42ef", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/ports/types.ts": "f4dbd1a3f4b7f539b3a85418617d25adbf710b54144161880d48f6c4ec032eee", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/ports/types/platform.ts": "0ecffeda71919293f9ffdb6c564ddea4f23bc85c4e640b08ea78225d34387fdc", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/ports/utils.ts": "6b14b331cce66bd46e7aec51f02424327d819150f16d3f72a6b0aaf7aee43c09", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/ports/worker.ts": "6b76ba1efb2e47a82582fc48bcc6264fe153a166beffccde1a9a3a185024c337", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/std.ts": "419d6b04680f73f7b252257ab287d68c1571cee4347301c53278e2b53df21c4a", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/tasks/deno.ts": "2b61092e13787df2e9b310702b78259d7af912d53b3957bc1c91a6669cdc53c0", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/tasks/exec.ts": "dd92c2d73f7e8d7f942799ec8216ff0e1b334b7ef3997af55a18edee1b6fe42a", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/tasks/inter.ts": "63e8f2860f7e3b4d95b6f61ca56aeb8567e4f265aa9c22cace6c8075edd6210f", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/tasks/mod.ts": "334b18d7c110cc05483be96353e342425c0033b7410c271a8a47d2b18308c73e", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/tasks/types.ts": "072a34bd0749428bad4d612cc86abe463d4d4f74dc56cf0a48a1f41650e2399b", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/modules/types.ts": "c0f212b686a2721d076e9aeb127596c7cbc939758e2cc32fd1d165a8fb320a87", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/port.ts": "c039a010dee7dfd978478cf4c5e2256c643135e10f33c30a09f8db9915e9d89d", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/act.ts": "2ce6b8fddf61db12ba69b7cad6985237a2962ca79853edbddee5bfb49c47d1ab", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/asdf.ts": "11d41bcad5981e014478343270f05bac265990e801c525e3288113d89bd287be", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/asdf_plugin_git.ts": "a3742fcd994ded231febf33754b087ab56393a799297b26315c2cf8a388a7f82", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/cargo-binstall.ts": "72860580e6f6db9ec7ba74dbe391ad98ed49b4ff43247661b27701f4e683d41b", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/cargobi.ts": "51c95fe47132ee35df2cd34c67d10d2e53dc10edd438c0f4f70eb644e81f2563", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/cmake.ts": "745bcfdbffdd5d7cb0314e4e618b764a3a0f7d19246ec8b9134b1ff981bc2091", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/cpy_bs.ts": "4066e5eb094e72be4dec2428fb7f99231dcc5c4e2db7b5ea2373a9ad9ce28662", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/curl.ts": "58acd2a158187f448d940f45bfcd87c9b4884db127dcbaaaef27258bb4ebce92", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/deno_ghrel.ts": "eca02a93ceb62ad9fb7f395361d32da0d5657aba5f7856c8ae0109135da0e070", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/earthly.ts": "7a3c8cae1631f670105a63bc41c47a49da6fc777968c0e9546c55d43fa418619", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/git.ts": "2e68f1fa5ba534ee32db204bcc357f987437dffe5d87c1a0a9c47850fa654419", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/infisical.ts": "77789ea377250f0e762d01f1a8d378636bc520e7291aa9e82c5321c4059b6205", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/jq_ghrel.ts": "f685342c833c96637732fb28556f411f4537e85292046473f2b0d7f28c66ec8c", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/meta_cli_ghrel.ts": "0d5128cd3a15eece3fdf49c0697f5354e37ede6388e058dde572699634df1464", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/mod.ts": "a25ca4a4ca5e640ef436195cd0b5d0b1be33fa7f770e2d40a8eec6fb2b23838a", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/mold.ts": "b916227b48d7aa307ccc7d54c66724a41baa67be82bd558b5b9a35db4179c2f3", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/node.ts": "a98a095d3405a4907acfb620ff43babb0771d51ecaed87af8d1816c1cecd009b", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/npmi.ts": "056c7e733b1157601647630b9d460f19b5416dccc20415dd275d7ce972f09c39", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/opentofu_ghrel.ts": "46ef05d30772d36b91d88a2dc1aae31e096c59ba6ecf82af08359996c1476725", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/pipi.ts": "a4abf1bd197f01e0fbc68bfb60bdb43849d9719654e1033820d3fb4ce4f36449", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/pnpm.ts": "41e7f473a687123ae96ab14a3a04f67ef0c4b44eea6747448826dbdae00bfdde", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/protoc.ts": "ef93af8f37d8186c2220b6d2f760b5da10decaa3e9fe7768003ee319d32335bf", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/ruff.ts": "2a3246bf3d21482ea62c3e801e58ef760fa6b30d4652e5be55c9051ecf6df72e", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/rust.ts": "e8b58f3ccb5411f0bba4bf3aadc040017da11eab4f6820cee03ab8f764383ca2", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/rustup.ts": "0a8033d24fb6be84585db545b2592b868d36182907565fe23454e9a0262618cc", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/tar.ts": "c3c43a99f8a9b1c160286cbc6240f59658d994856eeacaee479f645ece44d6c4", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/temporal_cli.ts": "a5db59114e294b58715349e72e3d7e868274d4a25d7de027afa0470c5585ed9c", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/terraform.ts": "035fbdd3a6b858bd302c440fc0a588fb40ae57959685af837f8a4e34302b55a7", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/unzip.ts": "c4559c627246f9c051571bbdff8c63ab15780ffd9e71656a9055488cc3bf32c3", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/wasmedge.ts": "b74a35190b79be686d2c8615c291b883da21e0caea36a8a32340fba93694b8e0", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/ports/zstd.ts": "fb8334b7b43ef34ba60ad391460e2fabb62889f77eade7798c823b14842cea45", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/setup_logger.ts": "f8a206bda0595497d6f4718032d4a959000b32ef3346d4b507777eec6a169458", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/std.ts": "74515b1d816e643860b2a94409a49c08d8478d756c1fcae0dce95dde2c5c7162", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/std/copyLock.ts": "a47725f058cc8120914629bd0d4488345f168e80f1b3b286a64d4d1e919d6599", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/std/sedLock.ts": "115bcf40bb13435e579df24919d1a0f9be3d3ec96c442812c9ae4ceb335932aa", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/utils/logger.ts": "fcbafb35ae4b812412b9b301ce6d06b8b9798f94ebebe3f92677e25e4b19af3c", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/utils/mod.ts": "647acf23e785d1a44b1d1da53d238fa9d921ccf841b5e65d01c8be0589118395", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/utils/unarchive.ts": "f6d0e9e75f470eeef5aecd0089169f4350fc30ebfdc05466bb7b30042294d6d3", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/utils/url.ts": "e1ada6fd30fc796b8918c88456ea1b5bbd87a07d0a0538b092b91fd2bb9b7623", - "https://raw.githubusercontent.com/metatypedev/ghjk/b702292/utils/worker.ts": "ac4caf72a36d2e4af4f4e92f2e0a95f9fc2324b568640f24c7c2ff6dc0c11d62" + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/deps/cli.ts": "aac025f9372ad413b9c2663dc7f61affd597820d9448f010a510d541df3b56ea", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/deps/common.ts": "f775710b66a9099b98651cd3831906466e9b83ef98f2e5c080fd59ee801c28d4", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/deps/ports.ts": "3c60d1f7ab626ffdd81b37f4e83a780910936480da8fe24f4ccceaefa207d339", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/files/mod.ts": "44a8874c6ee9f086b7a521d4956c1802be201d01f9e91329d52a4b96738f7a34", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/host/types.ts": "f450d9b9c0eced2650262d02455aa6f794de0edd6b052aade256882148e5697f", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/install/mod.ts": "aa54eb3e119f28d33e61645c89669da292ee00376068ead8f45be2807e7a9989", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/install/utils.ts": "d4634d4fc0e963f540402b4ca7eb5dcba340eaa0d8fceb43af57d722ad267115", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/mod.ts": "1d31b4f801ae2ebad052d219236699c4f227b6ce53c6e5016deaed5fcd00dbb6", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/envs/inter.ts": "84805fa208754a08f185dca7a5236de3760bbc1d0df96af86ea5fd7778f827a2", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/envs/mod.ts": "5f37b9f155808f8d6d51e1f16f58c07914d8c7d8070bc5c2fb5076ab748798a7", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/envs/posix.ts": "b22f9564d9773548d537c95265e694a2630c3fe1fd63354d6f4790e275545299", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/envs/reducer.ts": "76ee6974c9d4885da0898e01c498dcfdd99a3652a5a564d679577931a680e781", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/envs/types.ts": "9ff28d47aa60042df42fbb98a46f7689d8111be462237f5fb81771011e429088", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/mod.ts": "fc1cb9176c6557b44ae9c6536fa51c6c4f80ac01fc476d15b0a217e70cb0d176", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/ambient.ts": "823ec8d98702a60e6bfcdbeb64b69dc9f5039e73a1f10e87cd51210c1aaf52d5", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/base.ts": "8ef8a8de372420bddcd63a1b363937f43d898059e99478a58621e8432bcd5891", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/db.ts": "a309d1058f66079a481141c3f1733d928b9af8a37b7ce911b1228f70fd24df0f", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/ghrel.ts": "ebbc30a5c31244131d937eadca73fbc099c9e7bdf0ad4f668766d4388ede143c", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/inter.ts": "b3999e73d73d7f928a8de86e5e2261fe6b1450ceedfb54f24537bf0803532ed0", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/mod.ts": "646cfe12c181f378ffd865890e07ba0a2c92b70cf10687f43de49864ca15c482", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/reducers.ts": "d04e813652101f67f946242df68429ed5540e499fbdb7776b8be5703f16754c8", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/std.ts": "985902519aafef6e8e6aecc8922e70abdea5b8e97d5439bf94338b93242fe11f", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/sync.ts": "a7a297f6b098360d56af168692f3cff96f8ceeb5189e5baa249e094f8d9c42ef", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/types.ts": "f4dbd1a3f4b7f539b3a85418617d25adbf710b54144161880d48f6c4ec032eee", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/types/platform.ts": "0ecffeda71919293f9ffdb6c564ddea4f23bc85c4e640b08ea78225d34387fdc", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/utils.ts": "6b14b331cce66bd46e7aec51f02424327d819150f16d3f72a6b0aaf7aee43c09", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/ports/worker.ts": "6b76ba1efb2e47a82582fc48bcc6264fe153a166beffccde1a9a3a185024c337", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/std.ts": "419d6b04680f73f7b252257ab287d68c1571cee4347301c53278e2b53df21c4a", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/tasks/deno.ts": "75b85d8cdc129e56d7bd1bfbfdc4a6f4685e86933c41908e48fbc51be7a57fee", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/tasks/exec.ts": "ddc6bc7cbed464fdd94038a0df8668138411e94e49ae639615b93e734e37d311", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/tasks/inter.ts": "63e8f2860f7e3b4d95b6f61ca56aeb8567e4f265aa9c22cace6c8075edd6210f", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/tasks/mod.ts": "334b18d7c110cc05483be96353e342425c0033b7410c271a8a47d2b18308c73e", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/tasks/types.ts": "072a34bd0749428bad4d612cc86abe463d4d4f74dc56cf0a48a1f41650e2399b", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/modules/types.ts": "c0f212b686a2721d076e9aeb127596c7cbc939758e2cc32fd1d165a8fb320a87", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/port.ts": "c039a010dee7dfd978478cf4c5e2256c643135e10f33c30a09f8db9915e9d89d", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/act.ts": "2ce6b8fddf61db12ba69b7cad6985237a2962ca79853edbddee5bfb49c47d1ab", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/asdf.ts": "11d41bcad5981e014478343270f05bac265990e801c525e3288113d89bd287be", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/asdf_plugin_git.ts": "a3742fcd994ded231febf33754b087ab56393a799297b26315c2cf8a388a7f82", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/cargo-binstall.ts": "72860580e6f6db9ec7ba74dbe391ad98ed49b4ff43247661b27701f4e683d41b", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/cargobi.ts": "51c95fe47132ee35df2cd34c67d10d2e53dc10edd438c0f4f70eb644e81f2563", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/cmake.ts": "745bcfdbffdd5d7cb0314e4e618b764a3a0f7d19246ec8b9134b1ff981bc2091", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/cpy_bs.ts": "4066e5eb094e72be4dec2428fb7f99231dcc5c4e2db7b5ea2373a9ad9ce28662", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/curl.ts": "58acd2a158187f448d940f45bfcd87c9b4884db127dcbaaaef27258bb4ebce92", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/deno_ghrel.ts": "eca02a93ceb62ad9fb7f395361d32da0d5657aba5f7856c8ae0109135da0e070", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/earthly.ts": "7a3c8cae1631f670105a63bc41c47a49da6fc777968c0e9546c55d43fa418619", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/git.ts": "2e68f1fa5ba534ee32db204bcc357f987437dffe5d87c1a0a9c47850fa654419", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/infisical.ts": "77789ea377250f0e762d01f1a8d378636bc520e7291aa9e82c5321c4059b6205", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/jq_ghrel.ts": "f685342c833c96637732fb28556f411f4537e85292046473f2b0d7f28c66ec8c", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/meta_cli_ghrel.ts": "0d5128cd3a15eece3fdf49c0697f5354e37ede6388e058dde572699634df1464", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/mod.ts": "a25ca4a4ca5e640ef436195cd0b5d0b1be33fa7f770e2d40a8eec6fb2b23838a", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/mold.ts": "b916227b48d7aa307ccc7d54c66724a41baa67be82bd558b5b9a35db4179c2f3", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/node.ts": "a98a095d3405a4907acfb620ff43babb0771d51ecaed87af8d1816c1cecd009b", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/npmi.ts": "056c7e733b1157601647630b9d460f19b5416dccc20415dd275d7ce972f09c39", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/opentofu_ghrel.ts": "46ef05d30772d36b91d88a2dc1aae31e096c59ba6ecf82af08359996c1476725", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/pipi.ts": "a4abf1bd197f01e0fbc68bfb60bdb43849d9719654e1033820d3fb4ce4f36449", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/pnpm.ts": "41e7f473a687123ae96ab14a3a04f67ef0c4b44eea6747448826dbdae00bfdde", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/protoc.ts": "ef93af8f37d8186c2220b6d2f760b5da10decaa3e9fe7768003ee319d32335bf", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/ruff.ts": "2a3246bf3d21482ea62c3e801e58ef760fa6b30d4652e5be55c9051ecf6df72e", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/rust.ts": "e8b58f3ccb5411f0bba4bf3aadc040017da11eab4f6820cee03ab8f764383ca2", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/rustup.ts": "0a8033d24fb6be84585db545b2592b868d36182907565fe23454e9a0262618cc", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/tar.ts": "c3c43a99f8a9b1c160286cbc6240f59658d994856eeacaee479f645ece44d6c4", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/temporal_cli.ts": "a5db59114e294b58715349e72e3d7e868274d4a25d7de027afa0470c5585ed9c", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/terraform.ts": "035fbdd3a6b858bd302c440fc0a588fb40ae57959685af837f8a4e34302b55a7", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/unzip.ts": "c4559c627246f9c051571bbdff8c63ab15780ffd9e71656a9055488cc3bf32c3", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/wasmedge.ts": "b74a35190b79be686d2c8615c291b883da21e0caea36a8a32340fba93694b8e0", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/ports/zstd.ts": "fb8334b7b43ef34ba60ad391460e2fabb62889f77eade7798c823b14842cea45", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/setup_logger.ts": "f8a206bda0595497d6f4718032d4a959000b32ef3346d4b507777eec6a169458", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/std.ts": "74515b1d816e643860b2a94409a49c08d8478d756c1fcae0dce95dde2c5c7162", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/std/copyLock.ts": "a47725f058cc8120914629bd0d4488345f168e80f1b3b286a64d4d1e919d6599", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/std/sedLock.ts": "115bcf40bb13435e579df24919d1a0f9be3d3ec96c442812c9ae4ceb335932aa", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/utils/logger.ts": "fcbafb35ae4b812412b9b301ce6d06b8b9798f94ebebe3f92677e25e4b19af3c", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/utils/mod.ts": "25901b5a03625353cc0d9c024daca806eb2513b153faede5ecad73b428542721", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/utils/unarchive.ts": "f6d0e9e75f470eeef5aecd0089169f4350fc30ebfdc05466bb7b30042294d6d3", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/utils/url.ts": "e1ada6fd30fc796b8918c88456ea1b5bbd87a07d0a0538b092b91fd2bb9b7623", + "https://raw.githubusercontent.com/metatypedev/ghjk/v0.2.1/utils/worker.ts": "ac4caf72a36d2e4af4f4e92f2e0a95f9fc2324b568640f24c7c2ff6dc0c11d62" } } diff --git a/typegate/engine/build.rs b/typegate/engine/build.rs index 453d5febe7..af7ed59def 100644 --- a/typegate/engine/build.rs +++ b/typegate/engine/build.rs @@ -12,7 +12,6 @@ fn main() -> Result<(), Box> { let cwd = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?); let out_dir = PathBuf::from(std::env::var("OUT_DIR")?); let target = std::env::var("TARGET")?; - println!( "cargo:rerun-if-changed={}/main.py", cwd.join("../../libs/pyrt_wit_wire") @@ -29,9 +28,11 @@ fn main() -> Result<(), Box> { ); let wasm_path = out_dir.join("pyrt.wasm"); - // note: we're using ghjk here + // NOTE: we're using ghjk here // if this proves troubsome, consider moving the // task impl inline + // NOTE: you'll need to manually invalidate the cache + // if you change the build-pyrt behaviour assert!( std::process::Command::new("ghjk") .args(["x", "build-pyrt"]) diff --git a/typegate/import_map.json b/typegate/import_map.json index fbf6c4a22b..502cc83f00 100644 --- a/typegate/import_map.json +++ b/typegate/import_map.json @@ -31,7 +31,7 @@ "@typegate/": "./src/", "test-utils/": "./tests/utils/", "@dev/": "../dev/", - "dax": "https://deno.land/x/dax@0.39.2/mod.ts", + "dax": "jsr:@david/dax@0.41.0", "dispose": "https://deno.land/x/dispose@1.1.0/mod.ts", "download": "https://deno.land/x/download@v1.0.1/mod.ts" } diff --git a/typegate/tests/metagen/__snapshots__/metagen_test.ts.snap b/typegate/tests/metagen/__snapshots__/metagen_test.ts.snap index 81f78013c5..a26fc3186a 100644 --- a/typegate/tests/metagen/__snapshots__/metagen_test.ts.snap +++ b/typegate/tests/metagen/__snapshots__/metagen_test.ts.snap @@ -5,18 +5,146 @@ snapshot[`Metagen within sdk 1`] = ` [ "0", { - content: "from dataclasses import dataclass -from typing import Union + content: 'from types import NoneType +from typing import Callable, List, Union, get_origin, ForwardRef, Any +from dataclasses import dataclass, asdict, fields + +FORWARD_REFS = {} + +class Struct: + def repr(self): + return asdict(self) + + @staticmethod + def try_new(dt_class, val: Any): + # Object + ftypes = {f.name: f.type for f in fields(dt_class)} + attrs = {} + for f in val: + fval = val[f] + ftype = ftypes[f] + serialized = False + # Union + if get_origin(ftype) is Union: + try: + attrs[f] = Struct.try_union(ftype.__args__, fval) + serialized = True + except Exception: + pass + # List + elif get_origin(ftype) is list: + try: + attrs[f] = Struct.try_typed_list(ftype.__args__, fval) + serialized = True + except Exception: + pass + # Any + if not serialized: + if isinstance(ftype, str) and ftype in FORWARD_REFS: + klass = FORWARD_REFS[ftype] + attrs[f] = Struct.new(klass, fval) + else: + attrs[f] = Struct.new(ftype, fval) + return dt_class(**attrs) + + @staticmethod + def try_typed_list(tpe: Any, items: Any): + hint = tpe.__args__[0] + klass = ( + FORWARD_REFS[hint.__forward_arg__] if isinstance(hint, ForwardRef) else hint + ) + return [Struct.new(klass, v) for v in items] + + @staticmethod + def try_union(variants: List[Any], val: Any): + errors = [] + for variant in variants: + try: + if variant is NoneType: + if val is None: + return None + else: + continue + if get_origin(variant) is list: + if isinstance(val, list): + return Struct.try_typed_list(variant, val) + else: + continue + klass = FORWARD_REFS[variant.__forward_arg__] + return Struct.try_new(klass, val) + except Exception as e: + errors.append(str(e)) + raise Exception("\\\\n".join(errors)) + + @staticmethod + def new(dt_class: Any, val: Any): + try: + return Struct.try_new(dt_class, val) + except Exception: + return val @dataclass -class RetryStrategy: - max_retries: int - initial_backoff_interval: Union[int, None] - max_backoff_interval: Union[int, None] -", +class Object7(Struct): + name: str + + +FORWARD_REFS["Object7"] = Object7 + + +@dataclass +class Student(Struct): + id: int + name: str + peers: Union[List["Student"], None] + + +FORWARD_REFS["Student"] = Student + + +@dataclass +class TwoInput(Struct): + name: str + + +FORWARD_REFS["TwoInput"] = TwoInput + + +Type8Student = List["Student"] +TypeString6 = str + +def __repr(value: Any): + if isinstance(value, Struct): + return value.repr() + return value + + + +def typed_fnOne(user_fn: Callable[[Object7], Type8Student]): + def exported_wrapper(raw_inp): + inp: Object7 = Struct.new(Object7, raw_inp) + out: Type8Student = user_fn(inp) + if isinstance(out, list): + return [__repr(v) for v in out] + return __repr(out) + + return exported_wrapper + + +def typed_fnTwo(user_fn: Callable[[TwoInput], TypeString6]): + def exported_wrapper(raw_inp): + inp: TwoInput = Struct.new(TwoInput, raw_inp) + out: TypeString6 = user_fn(inp) + if isinstance(out, list): + return [__repr(v) for v in out] + return __repr(out) + + return exported_wrapper + + +', overwrite: true, - path: "./workspace/some/base/path/ts/types.py", + path: "./workspace/some/base/path/python/same_hit_types.py", }, ], [ @@ -28,7 +156,7 @@ from dataclasses import dataclass, asdict, fields FORWARD_REFS = {} -class Struct(): +class Struct: def repr(self): return asdict(self) @@ -100,13 +228,15 @@ class Struct(): except Exception: return val + @dataclass class Object7(Struct): name: str - FORWARD_REFS["Object7"] = Object7 + + @dataclass class Student(Struct): id: int @@ -114,16 +244,17 @@ class Student(Struct): peers: Union[List["Student"], None] - FORWARD_REFS["Student"] = Student + def __repr(value: Any): if isinstance(value, Struct): return value.repr() return value + def typed_three(user_fn: Callable[[Object7], Student]): def exported_wrapper(raw_inp): inp: Object7 = Struct.new(Object7, raw_inp) @@ -131,6 +262,7 @@ def typed_three(user_fn: Callable[[Object7], Student]): if isinstance(out, list): return [__repr(v) for v in out] return __repr(out) + return exported_wrapper @@ -141,6 +273,99 @@ def typed_three(user_fn: Callable[[Object7], Student]): ], [ "10", + { + content: '# - NOTE: only modules that are imported relatively +# are supported. I.e. prefixed by \`.\` or \`..\` +# - Make sure to include any module imports in the \`deps\` +# array when using external modules with PythonRuntime +from .same_hit_types import Object7, TwoInput, Type8Student, TypeString6, typed_fnOne, typed_fnTwo + + +@typed_fnOne +def fnOne(inp: Object7) -> Type8Student: + # TODO: write your logic here + raise Exception("fnOne not implemented") + +@typed_fnTwo +def fnTwo(inp: TwoInput) -> TypeString6: + # TODO: write your logic here + raise Exception("fnTwo not implemented") + +', + overwrite: false, + path: "./workspace/some/base/path/python/same_hit.py", + }, + ], + [ + "2", + { + content: "from dataclasses import dataclass +from typing import Union + + +@dataclass +class RetryStrategy: + max_retries: int + initial_backoff_interval: Union[int, None] + max_backoff_interval: Union[int, None] +", + overwrite: true, + path: "./workspace/some/base/path/ts/types.py", + }, + ], + [ + "3", + { + content: " +mod mdk; +pub use mdk::*; + +/* +init_mat! { + hook: || { + // initialize global stuff here if you need it + MatBuilder::new() + // register function handlers here + .register_handler(stubs::MyFunc::erased(MyMat)) + } +} + +struct MyMat; + +// FIXME: use actual types from your mdk here +impl stubs::MyFunc for MyMat { + fn handle(&self, input: types::MyFuncIn, _cx: Ctx) -> anyhow::Result { + unimplemented!() + } +} +*/ +", + overwrite: false, + path: "./workspace/some/base/path/rust/lib.rs", + }, + ], + [ + "4", + { + content: '# - NOTE: only modules that are imported relatively +# are supported. I.e. prefixed by \`.\` or \`..\` +# - Make sure to include any module imports in the \`deps\` +# array when using external modules with PythonRuntime +from .other_types import Object7, Student, typed_three + + +@typed_three +def three(inp: Object7) -> Student: + # TODO: write your logic here + raise Exception("three not implemented") + +', + overwrite: false, + path: "./workspace/some/base/path/python/other.py", + }, + ], + [ + "5", { content: '// This file was @generated by metagen and is intended // to be generated again on subsequent metagen runs. @@ -185,7 +410,7 @@ export type Student43 = Student3 | null | undefined; export type Student = { id: number; name: string; - peers: Student43; + peers?: Student43; }; export type Student8 = Array; export type TwoInput = { @@ -202,14 +427,14 @@ export type Func20Handler = Handler; }, ], [ - "2", + "6", { content: \`// This file was @generated by metagen and is intended // to be generated again on subsequent metagen runs. #![cfg_attr(rustfmt, rustfmt_skip)] // gen-static-start -#![allow(unused)] +#![allow(dead_code)] pub mod wit { wit_bindgen::generate!({ @@ -425,7 +650,6 @@ macro_rules! init_mat { // gen-static-end use types::*; pub mod types { - use super::*; #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct Object7 { pub name: String, @@ -444,7 +668,6 @@ pub mod types { pub name: String, } } -use stubs::*; pub mod stubs { use super::*; pub trait Func18: Sized + 'static { @@ -519,27 +742,22 @@ pub mod stubs { }, ], [ - "3", + "7", { - content: '# - NOTE: only modules that are imported relatively -# are supported. I.e. prefixed by \`.\` or \`..\` -# - Make sure to include any module imports in the \`deps\` -# array when using external modules with PythonRuntime -from .other_types import typed_three, Object7, Student + content: "from substantial import workflow, Context # noqa +from substantial.types import RetryStrategy # noqa -@typed_three -def three(inp: Object7) -> Student: - # TODO: write your logic here - raise Exception("three not implemented") - -', +@workflow() +def workflow_name(c: Context): + raise NotImplementedError +", overwrite: false, - path: "./workspace/some/base/path/python/other.py", + path: "./workspace/some/base/path/ts/workflow.py", }, ], [ - "4", + "8", { content: 'package.name = "example_metagen_mdk" package.edition = "2021" @@ -564,32 +782,7 @@ opt-level = "z"', }, ], [ - "5", - { - content: '# - NOTE: only modules that are imported relatively -# are supported. I.e. prefixed by \`.\` or \`..\` -# - Make sure to include any module imports in the \`deps\` -# array when using external modules with PythonRuntime -from .same_hit_types import Object7, typed_fnOne, typed_fnTwo, Type8Student, TwoInput, TypeString6 - - -@typed_fnOne -def fnOne(inp: Object7) -> Type8Student: - # TODO: write your logic here - raise Exception("fnOne not implemented") - -@typed_fnTwo -def fnTwo(inp: TwoInput) -> TypeString6: - # TODO: write your logic here - raise Exception("fnTwo not implemented") - -', - overwrite: false, - path: "./workspace/some/base/path/python/same_hit.py", - }, - ], - [ - "6", + "9", { content: "from datetime import timedelta from typing import Any, Callable, Optional @@ -629,54 +822,13 @@ def workflow(): path: "./workspace/some/base/path/ts/substantial.py", }, ], - [ - "7", - { - content: "from substantial import workflow, Context # noqa -from substantial.types import RetryStrategy # noqa - - -@workflow() -def workflow_name(c: Context): - raise NotImplementedError -", - overwrite: false, - path: "./workspace/some/base/path/ts/workflow.py", - }, - ], - [ - "8", - { - content: " -mod mdk; -pub use mdk::*; - -/* -init_mat! { - hook: || { - // initialize global stuff here if you need it - MatBuilder::new() - // register function handlers here - .register_handler(stubs::MyFunc::erased(MyMat)) - } -} - -struct MyMat; +] +`; -// FIXME: use actual types from your mdk here -impl stubs::MyFunc for MyMat { - fn handle(&self, input: types::MyFuncIn, _cx: Ctx) -> anyhow::Result { - unimplemented!() - } -} -*/ -", - overwrite: false, - path: "./workspace/some/base/path/rust/lib.rs", - }, - ], +snapshot[`Metagen within sdk 2`] = ` +[ [ - "9", + "0", { content: 'from types import NoneType from typing import Callable, List, Union, get_origin, ForwardRef, Any @@ -684,7 +836,7 @@ from dataclasses import dataclass, asdict, fields FORWARD_REFS = {} -class Struct(): +class Struct: def repr(self): return asdict(self) @@ -756,13 +908,15 @@ class Struct(): except Exception: return val + @dataclass class Object7(Struct): name: str - FORWARD_REFS["Object7"] = Object7 + + @dataclass class Student(Struct): id: int @@ -770,25 +924,27 @@ class Student(Struct): peers: Union[List["Student"], None] - FORWARD_REFS["Student"] = Student + + @dataclass class TwoInput(Struct): name: str - FORWARD_REFS["TwoInput"] = TwoInput + + Type8Student = List["Student"] TypeString6 = str - def __repr(value: Any): if isinstance(value, Struct): return value.repr() return value + def typed_fnOne(user_fn: Callable[[Object7], Type8Student]): def exported_wrapper(raw_inp): inp: Object7 = Struct.new(Object7, raw_inp) @@ -796,6 +952,7 @@ def typed_fnOne(user_fn: Callable[[Object7], Type8Student]): if isinstance(out, list): return [__repr(v) for v in out] return __repr(out) + return exported_wrapper @@ -806,6 +963,7 @@ def typed_fnTwo(user_fn: Callable[[TwoInput], TypeString6]): if isinstance(out, list): return [__repr(v) for v in out] return __repr(out) + return exported_wrapper @@ -814,28 +972,6 @@ def typed_fnTwo(user_fn: Callable[[TwoInput], TypeString6]): path: "./workspace/some/base/path/python/same_hit_types.py", }, ], -] -`; - -snapshot[`Metagen within sdk 2`] = ` -[ - [ - "0", - { - content: "from dataclasses import dataclass -from typing import Union - - -@dataclass -class RetryStrategy: - max_retries: int - initial_backoff_interval: Union[int, None] - max_backoff_interval: Union[int, None] -", - overwrite: true, - path: "./workspace/some/base/path/ts/types.py", - }, - ], [ "1", { @@ -845,7 +981,7 @@ from dataclasses import dataclass, asdict, fields FORWARD_REFS = {} -class Struct(): +class Struct: def repr(self): return asdict(self) @@ -917,13 +1053,15 @@ class Struct(): except Exception: return val + @dataclass class Object7(Struct): name: str - FORWARD_REFS["Object7"] = Object7 + + @dataclass class Student(Struct): id: int @@ -931,16 +1069,17 @@ class Student(Struct): peers: Union[List["Student"], None] - FORWARD_REFS["Student"] = Student + def __repr(value: Any): if isinstance(value, Struct): return value.repr() return value + def typed_three(user_fn: Callable[[Object7], Student]): def exported_wrapper(raw_inp): inp: Object7 = Struct.new(Object7, raw_inp) @@ -948,6 +1087,7 @@ def typed_three(user_fn: Callable[[Object7], Student]): if isinstance(out, list): return [__repr(v) for v in out] return __repr(out) + return exported_wrapper @@ -958,6 +1098,99 @@ def typed_three(user_fn: Callable[[Object7], Student]): ], [ "10", + { + content: '# - NOTE: only modules that are imported relatively +# are supported. I.e. prefixed by \`.\` or \`..\` +# - Make sure to include any module imports in the \`deps\` +# array when using external modules with PythonRuntime +from .same_hit_types import Object7, TwoInput, Type8Student, TypeString6, typed_fnOne, typed_fnTwo + + +@typed_fnOne +def fnOne(inp: Object7) -> Type8Student: + # TODO: write your logic here + raise Exception("fnOne not implemented") + +@typed_fnTwo +def fnTwo(inp: TwoInput) -> TypeString6: + # TODO: write your logic here + raise Exception("fnTwo not implemented") + +', + overwrite: false, + path: "./workspace/some/base/path/python/same_hit.py", + }, + ], + [ + "2", + { + content: "from dataclasses import dataclass +from typing import Union + + +@dataclass +class RetryStrategy: + max_retries: int + initial_backoff_interval: Union[int, None] + max_backoff_interval: Union[int, None] +", + overwrite: true, + path: "./workspace/some/base/path/ts/types.py", + }, + ], + [ + "3", + { + content: " +mod mdk; +pub use mdk::*; + +/* +init_mat! { + hook: || { + // initialize global stuff here if you need it + MatBuilder::new() + // register function handlers here + .register_handler(stubs::MyFunc::erased(MyMat)) + } +} + +struct MyMat; + +// FIXME: use actual types from your mdk here +impl stubs::MyFunc for MyMat { + fn handle(&self, input: types::MyFuncIn, _cx: Ctx) -> anyhow::Result { + unimplemented!() + } +} +*/ +", + overwrite: false, + path: "./workspace/some/base/path/rust/lib.rs", + }, + ], + [ + "4", + { + content: '# - NOTE: only modules that are imported relatively +# are supported. I.e. prefixed by \`.\` or \`..\` +# - Make sure to include any module imports in the \`deps\` +# array when using external modules with PythonRuntime +from .other_types import Object7, Student, typed_three + + +@typed_three +def three(inp: Object7) -> Student: + # TODO: write your logic here + raise Exception("three not implemented") + +', + overwrite: false, + path: "./workspace/some/base/path/python/other.py", + }, + ], + [ + "5", { content: '// This file was @generated by metagen and is intended // to be generated again on subsequent metagen runs. @@ -1002,7 +1235,7 @@ export type Student43 = Student3 | null | undefined; export type Student = { id: number; name: string; - peers: Student43; + peers?: Student43; }; export type Student8 = Array; export type TwoInput = { @@ -1019,14 +1252,14 @@ export type Func20Handler = Handler; }, ], [ - "2", + "6", { content: \`// This file was @generated by metagen and is intended // to be generated again on subsequent metagen runs. #![cfg_attr(rustfmt, rustfmt_skip)] // gen-static-start -#![allow(unused)] +#![allow(dead_code)] pub mod wit { wit_bindgen::generate!({ @@ -1242,7 +1475,6 @@ macro_rules! init_mat { // gen-static-end use types::*; pub mod types { - use super::*; #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct Object7 { pub name: String, @@ -1261,7 +1493,6 @@ pub mod types { pub name: String, } } -use stubs::*; pub mod stubs { use super::*; pub trait Func18: Sized + 'static { @@ -1336,27 +1567,22 @@ pub mod stubs { }, ], [ - "3", + "7", { - content: '# - NOTE: only modules that are imported relatively -# are supported. I.e. prefixed by \`.\` or \`..\` -# - Make sure to include any module imports in the \`deps\` -# array when using external modules with PythonRuntime -from .other_types import typed_three, Object7, Student + content: "from substantial import workflow, Context # noqa +from substantial.types import RetryStrategy # noqa -@typed_three -def three(inp: Object7) -> Student: - # TODO: write your logic here - raise Exception("three not implemented") - -', +@workflow() +def workflow_name(c: Context): + raise NotImplementedError +", overwrite: false, - path: "./workspace/some/base/path/python/other.py", + path: "./workspace/some/base/path/ts/workflow.py", }, ], [ - "4", + "8", { content: 'package.name = "example_metagen_mdk" package.edition = "2021" @@ -1381,32 +1607,7 @@ opt-level = "z"', }, ], [ - "5", - { - content: '# - NOTE: only modules that are imported relatively -# are supported. I.e. prefixed by \`.\` or \`..\` -# - Make sure to include any module imports in the \`deps\` -# array when using external modules with PythonRuntime -from .same_hit_types import Object7, typed_fnOne, typed_fnTwo, Type8Student, TwoInput, TypeString6 - - -@typed_fnOne -def fnOne(inp: Object7) -> Type8Student: - # TODO: write your logic here - raise Exception("fnOne not implemented") - -@typed_fnTwo -def fnTwo(inp: TwoInput) -> TypeString6: - # TODO: write your logic here - raise Exception("fnTwo not implemented") - -', - overwrite: false, - path: "./workspace/some/base/path/python/same_hit.py", - }, - ], - [ - "6", + "9", { content: "from datetime import timedelta from typing import Any, Callable, Optional @@ -1446,190 +1647,5 @@ def workflow(): path: "./workspace/some/base/path/ts/substantial.py", }, ], - [ - "7", - { - content: "from substantial import workflow, Context # noqa -from substantial.types import RetryStrategy # noqa - - -@workflow() -def workflow_name(c: Context): - raise NotImplementedError -", - overwrite: false, - path: "./workspace/some/base/path/ts/workflow.py", - }, - ], - [ - "8", - { - content: " -mod mdk; -pub use mdk::*; - -/* -init_mat! { - hook: || { - // initialize global stuff here if you need it - MatBuilder::new() - // register function handlers here - .register_handler(stubs::MyFunc::erased(MyMat)) - } -} - -struct MyMat; - -// FIXME: use actual types from your mdk here -impl stubs::MyFunc for MyMat { - fn handle(&self, input: types::MyFuncIn, _cx: Ctx) -> anyhow::Result { - unimplemented!() - } -} -*/ -", - overwrite: false, - path: "./workspace/some/base/path/rust/lib.rs", - }, - ], - [ - "9", - { - content: 'from types import NoneType -from typing import Callable, List, Union, get_origin, ForwardRef, Any -from dataclasses import dataclass, asdict, fields - -FORWARD_REFS = {} - -class Struct(): - def repr(self): - return asdict(self) - - @staticmethod - def try_new(dt_class, val: Any): - # Object - ftypes = {f.name: f.type for f in fields(dt_class)} - attrs = {} - for f in val: - fval = val[f] - ftype = ftypes[f] - serialized = False - # Union - if get_origin(ftype) is Union: - try: - attrs[f] = Struct.try_union(ftype.__args__, fval) - serialized = True - except Exception: - pass - # List - elif get_origin(ftype) is list: - try: - attrs[f] = Struct.try_typed_list(ftype.__args__, fval) - serialized = True - except Exception: - pass - # Any - if not serialized: - if isinstance(ftype, str) and ftype in FORWARD_REFS: - klass = FORWARD_REFS[ftype] - attrs[f] = Struct.new(klass, fval) - else: - attrs[f] = Struct.new(ftype, fval) - return dt_class(**attrs) - - @staticmethod - def try_typed_list(tpe: Any, items: Any): - hint = tpe.__args__[0] - klass = ( - FORWARD_REFS[hint.__forward_arg__] if isinstance(hint, ForwardRef) else hint - ) - return [Struct.new(klass, v) for v in items] - - @staticmethod - def try_union(variants: List[Any], val: Any): - errors = [] - for variant in variants: - try: - if variant is NoneType: - if val is None: - return None - else: - continue - if get_origin(variant) is list: - if isinstance(val, list): - return Struct.try_typed_list(variant, val) - else: - continue - klass = FORWARD_REFS[variant.__forward_arg__] - return Struct.try_new(klass, val) - except Exception as e: - errors.append(str(e)) - raise Exception("\\\\n".join(errors)) - - @staticmethod - def new(dt_class: Any, val: Any): - try: - return Struct.try_new(dt_class, val) - except Exception: - return val - -@dataclass -class Object7(Struct): - name: str - - - -FORWARD_REFS["Object7"] = Object7 -@dataclass -class Student(Struct): - id: int - name: str - peers: Union[List["Student"], None] - - - -FORWARD_REFS["Student"] = Student -@dataclass -class TwoInput(Struct): - name: str - - - -FORWARD_REFS["TwoInput"] = TwoInput -Type8Student = List["Student"] -TypeString6 = str - - -def __repr(value: Any): - if isinstance(value, Struct): - return value.repr() - return value - - -def typed_fnOne(user_fn: Callable[[Object7], Type8Student]): - def exported_wrapper(raw_inp): - inp: Object7 = Struct.new(Object7, raw_inp) - out: Type8Student = user_fn(inp) - if isinstance(out, list): - return [__repr(v) for v in out] - return __repr(out) - return exported_wrapper - - -def typed_fnTwo(user_fn: Callable[[TwoInput], TypeString6]): - def exported_wrapper(raw_inp): - inp: TwoInput = Struct.new(TwoInput, raw_inp) - out: TypeString6 = user_fn(inp) - if isinstance(out, list): - return [__repr(v) for v in out] - return __repr(out) - return exported_wrapper - - -', - overwrite: true, - path: "./workspace/some/base/path/python/same_hit_types.py", - }, - ], ] `; diff --git a/typegate/tests/metagen/metagen_test.ts b/typegate/tests/metagen/metagen_test.ts index 3e0a6e8f03..14ff7eb13f 100644 --- a/typegate/tests/metagen/metagen_test.ts +++ b/typegate/tests/metagen/metagen_test.ts @@ -8,6 +8,8 @@ import { assertEquals } from "std/assert/mod.ts"; import { GraphQLQuery } from "../utils/query/graphql_query.ts"; import { JSONValue } from "../../src/utils.ts"; import { testDir } from "../utils/dir.ts"; +import $ from "dax"; +import { z as zod } from "zod"; const denoJson = resolve(testDir, "../deno.jsonc"); @@ -185,7 +187,7 @@ Meta.test("Metagen within sdk", async (t) => { } }); -Meta.test("metagen table suite", async (metaTest) => { +Meta.test("mdk table suite", async (metaTest) => { const scriptsPath = join(import.meta.dirname!, "typegraphs/identities"); const genCratePath = join(scriptsPath, "rs"); // const genPyPath = join(scriptsPath, "py"); @@ -447,3 +449,104 @@ Meta.test("metagen table suite", async (metaTest) => { }); } }); + +Meta.test({ + name: "client table suite", +}, async (metaTest) => { + const scriptsPath = join(import.meta.dirname!, "typegraphs/sample"); + + assertEquals( + ( + await Meta.cli( + { + env: { + // RUST_BACKTRACE: "1", + }, + }, + ...`-C ${scriptsPath} gen`.split(" "), + ) + ).code, + 0, + ); + + const postSchema = zod.object({ + id: zod.string(), + slug: zod.string(), + title: zod.string(), + }); + const userSchema = zod.object({ + id: zod.string(), + email: zod.string(), + }); + const expectedSchemaQ = zod.object({ + user: userSchema.extend({ + post1: postSchema.array(), + post2: zod.object({ + // NOTE: no id + slug: zod.string(), + title: zod.string(), + }).array(), + }), + posts: postSchema, + scalarNoArgs: zod.string(), + }); + const expectedSchemaM = zod.object({ + scalarArgs: zod.string(), + compositeNoArgs: postSchema, + compositeArgs: postSchema, + }); + const expectedSchema = zod.tuple([ + expectedSchemaQ, + expectedSchemaQ, + expectedSchemaM, + expectedSchemaQ, + expectedSchemaM, + /* zod.object({ + scalarUnion: zod.string(), + compositeUnion1: postSchema, + compositeUnion2: zod.undefined(), + mixedUnion: zod.string(), + }), */ + ]); + const cases = [ + { + skip: false, + name: "client_ts", + // NOTE: dax replaces commands to deno with + // commands to xtask so we go through bah + command: $`bash -c "deno run -A main.ts"`.cwd( + join(scriptsPath, "ts"), + ), + expected: expectedSchema, + }, + { + name: "client_py", + command: $`python3 main.py`.cwd( + join(scriptsPath, "py"), + ), + expected: expectedSchema, + }, + { + name: "client_rs", + command: $`cargo run`.cwd( + join(scriptsPath, "rs"), + ), + expected: expectedSchema, + }, + ]; + + await using _engine = await metaTest.engine( + "metagen/typegraphs/sample.ts", + ); + for (const { name, command, expected, skip } of cases) { + if (skip) { + continue; + } + await metaTest.should(name, async () => { + const res = await command + .env({ "TG_PORT": metaTest.port.toString() }).text(); + console.log(res); + expected.parse(JSON.parse(res)); + }); + } +}); diff --git a/typegate/tests/metagen/typegraphs/identities/py/handlers_types.py b/typegate/tests/metagen/typegraphs/identities/py/handlers_types.py index 2048f5dfc0..963935686b 100644 --- a/typegate/tests/metagen/typegraphs/identities/py/handlers_types.py +++ b/typegate/tests/metagen/typegraphs/identities/py/handlers_types.py @@ -117,7 +117,7 @@ class CompositesArgs(Struct): class Composites(Struct): opt: Union[str, None] either: Union["Branch2", "Primitives"] - union: Union[List[str], str, int] + union: Union[List[str], int, str] list: List[str] @@ -144,7 +144,7 @@ class Cycles1Args(Struct): class Cycles1(Struct): phantom1: Union[str, None] to2: Union[Union["Cycles1", Union["Branch33A", "Branch33B"]], None] - list3: Union[List[Union["Branch33B", "Branch33A"]], None] + list3: Union[List[Union["Branch33A", "Branch33B"]], None] FORWARD_REFS["Cycles1"] = Cycles1 @@ -162,7 +162,7 @@ class Branch33A(Struct): @dataclass class Branch33B(Struct): phantom3b: Union[str, None] - to2: Union[Union[Union["Branch33A", "Branch33B"], "Cycles1"], None] + to2: Union[Union["Cycles1", Union["Branch33A", "Branch33B"]], None] FORWARD_REFS["Branch33B"] = Branch33B @@ -209,6 +209,17 @@ def __repr(value: Any): return value +def typed_primitives(user_fn: Callable[[PrimitivesArgs], Primitives]): + def exported_wrapper(raw_inp): + inp: PrimitivesArgs = Struct.new(PrimitivesArgs, raw_inp) + out: Primitives = user_fn(inp) + if isinstance(out, list): + return [__repr(v) for v in out] + return __repr(out) + + return exported_wrapper + + def typed_composites(user_fn: Callable[[CompositesArgs], Composites]): def exported_wrapper(raw_inp): inp: CompositesArgs = Struct.new(CompositesArgs, raw_inp) @@ -240,14 +251,3 @@ def exported_wrapper(raw_inp): return __repr(out) return exported_wrapper - - -def typed_primitives(user_fn: Callable[[PrimitivesArgs], Primitives]): - def exported_wrapper(raw_inp): - inp: PrimitivesArgs = Struct.new(PrimitivesArgs, raw_inp) - out: Primitives = user_fn(inp) - if isinstance(out, list): - return [__repr(v) for v in out] - return __repr(out) - - return exported_wrapper diff --git a/typegate/tests/metagen/typegraphs/identities/rs/mdk.rs b/typegate/tests/metagen/typegraphs/identities/rs/mdk.rs index 36250dc89a..fcd054bbb7 100644 --- a/typegate/tests/metagen/typegraphs/identities/rs/mdk.rs +++ b/typegate/tests/metagen/typegraphs/identities/rs/mdk.rs @@ -3,7 +3,7 @@ #![cfg_attr(rustfmt, rustfmt_skip)] // gen-static-start -#![allow(unused)] +#![allow(dead_code)] pub mod wit { wit_bindgen::generate!({ @@ -109,7 +109,7 @@ impl Router { } pub fn init(&self, args: InitArgs) -> Result { - static MT_VERSION: &str = "0.4.8-0"; + static MT_VERSION: &str = "0.4.8"; if args.metatype_version != MT_VERSION { return Err(InitError::VersionMismatch(MT_VERSION.into())); } @@ -219,27 +219,26 @@ macro_rules! init_mat { // gen-static-end use types::*; pub mod types { - use super::*; pub type String1 = String; - pub type StringUuid = String; - pub type StringEmail = String; - pub type StringEan = String; - pub type StringJson = String; - pub type StringUri = String; - pub type StringDate = String; - pub type StringDateTime = String; + pub type StringUuid6 = String; + pub type StringEmail7 = String; + pub type StringEan8 = String; + pub type StringJson9 = String; + pub type StringUri10 = String; + pub type StringDate11 = String; + pub type StringDateTime12 = String; #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct Primitives { pub str: String, #[serde(rename = "enum")] pub r#enum: String1, - pub uuid: StringUuid, - pub email: StringEmail, - pub ean: StringEan, - pub json: StringJson, - pub uri: StringUri, - pub date: StringDate, - pub datetime: StringDateTime, + pub uuid: StringUuid6, + pub email: StringEmail7, + pub ean: StringEan8, + pub json: StringJson9, + pub uri: StringUri10, + pub date: StringDate11, + pub datetime: StringDateTime12, pub int: i64, pub float: f64, pub boolean: bool, @@ -340,7 +339,6 @@ pub mod types { pub data: SimpleCycles1, } } -use stubs::*; pub mod stubs { use super::*; pub trait RsPrimitives: Sized + 'static { diff --git a/typegate/tests/metagen/typegraphs/identities/ts/mdk.ts b/typegate/tests/metagen/typegraphs/identities/ts/mdk.ts index 4d5c593d1f..cdb3e74cb2 100644 --- a/typegate/tests/metagen/typegraphs/identities/ts/mdk.ts +++ b/typegate/tests/metagen/typegraphs/identities/ts/mdk.ts @@ -34,23 +34,23 @@ export type Handler = ( ) => Out | Promise; export type String1 = "wan" | "tew" | "tree"; -export type StringUuid = string; -export type StringEmail = string; -export type StringEan = string; -export type StringJson = string; -export type StringUri = string; -export type StringDate = string; -export type StringDateTime = string; +export type StringUuid6 = string; +export type StringEmail7 = string; +export type StringEan8 = string; +export type StringJson9 = string; +export type StringUri10 = string; +export type StringDate11 = string; +export type StringDateTime12 = string; export type Primitives = { str: string; "enum": String1; - uuid: StringUuid; - email: StringEmail; - ean: StringEan; - json: StringJson; - uri: StringUri; - date: StringDate; - datetime: StringDateTime; + uuid: StringUuid6; + email: StringEmail7; + ean: StringEan8; + json: StringJson9; + uri: StringUri10; + date: StringDate11; + datetime: StringDateTime12; int: number; float: number; "boolean": boolean; @@ -73,7 +73,7 @@ export type Union27 = | (string) | (Branch4again); export type Composites = { - opt: (string) | null | undefined; + opt?: (string) | null | undefined; either: Either19; union: Union27; list: Array; @@ -83,13 +83,13 @@ export type CompositesArgs = { }; export type Cycles148 = Cycles1 | null | undefined; export type Branch33A = { - phantom3a: (string) | null | undefined; - to1: Cycles148; + phantom3a?: (string) | null | undefined; + to1?: Cycles148; }; export type Cycles254 = Cycles2 | null | undefined; export type Branch33B = { - phantom3b: (string) | null | undefined; - to2: Cycles254; + phantom3b?: (string) | null | undefined; + to2?: Cycles254; }; export type Cycles3 = | (Branch33A) @@ -101,27 +101,27 @@ export type Cycles235 = Cycles2 | null | undefined; export type Cycles337 = Array; export type Cycles33837 = Cycles337 | null | undefined; export type Cycles1 = { - phantom1: (string) | null | undefined; - to2: Cycles235; - list3: Cycles33837; + phantom1?: (string) | null | undefined; + to2?: Cycles235; + list3?: Cycles33837; }; export type Cycles1Args = { data: Cycles1; }; export type SimpleCycles174 = SimpleCycles1 | null | undefined; export type SimpleCycles3 = { - phantom3: (string) | null | undefined; - to1: SimpleCycles174; + phantom3?: (string) | null | undefined; + to1?: SimpleCycles174; }; export type SimpleCycles368 = SimpleCycles3 | null | undefined; export type SimpleCycles2 = { - phantom2: (string) | null | undefined; - to3: SimpleCycles368; + phantom2?: (string) | null | undefined; + to3?: SimpleCycles368; }; export type SimpleCycles262 = SimpleCycles2 | null | undefined; export type SimpleCycles1 = { - phantom1: (string) | null | undefined; - to2: SimpleCycles262; + phantom1?: (string) | null | undefined; + to2?: SimpleCycles262; }; export type SimpleCycles1Args = { data: SimpleCycles1; diff --git a/typegate/tests/metagen/typegraphs/sample.ts b/typegate/tests/metagen/typegraphs/sample.ts new file mode 100644 index 0000000000..345f9e8efb --- /dev/null +++ b/typegate/tests/metagen/typegraphs/sample.ts @@ -0,0 +1,95 @@ +// Copyright Metatype OÜ, licensed under the Elastic License 2.0. +// SPDX-License-Identifier: Elastic-2.0 + +import { fx, Policy, t, typegraph } from "@typegraph/sdk/index.ts"; +import { DenoRuntime } from "@typegraph/sdk/runtimes/deno.ts"; +import { RandomRuntime } from "@typegraph/sdk/runtimes/random.ts"; + +const genPost = () => ({ + id: "69099108-e48b-43c9-ad02-c6514eaad6e3", + slug: "hair", + title: "I dyed my hair!", +}); + +const _genPosts = () => [ + { slug: "hair", title: "I dyed my hair!" }, + { slug: "hello", title: "Hello World!" }, +]; + +export const tg = await typegraph({ + name: "sample", + builder(g) { + const random = new RandomRuntime({ seed: 0 }); + const deno = new DenoRuntime(); + + const post = t.struct({ + id: t.uuid({ asId: true, config: { auto: true } }), + slug: t.string(), + title: t.string(), + }, { name: "post" }); + + const user = t.struct({ + id: t.uuid({ asId: true, config: { auto: true } }), + email: t.email(), + posts: t.list(g.ref("post")), + }, { name: "user" }); + + /* const compositeUnion = t.union([post, user]); + const scalarUnion = t.union([t.string(), t.integer()]); + const mixedUnion = t.union([post, user, t.string(), t.integer()]); */ + + g.expose( + { + getUser: random.gen(user), + getPosts: random.gen(post), + + scalarNoArgs: random.gen(t.string()), + scalarArgs: deno.func( + post, + t.string(), + { + code: () => "hello", + effect: fx.update(), + }, + ), + compositeNoArgs: deno.func(t.struct({}), post, { + code: genPost, + effect: fx.update(), + }), + compositeArgs: deno.func( + t.struct({ id: t.string() }), + post, + { + code: genPost, + effect: fx.update(), + }, + ), + /* scalarUnion: deno.func( + t.struct({ id: t.string() }), + scalarUnion, + { + code: () => "hello", + }, + ), + compositeUnion: deno.func( + t.struct({ id: t.string() }), + compositeUnion, + { + code: genPost, + }, + ), + mixedUnion: deno.func( + t.struct({ id: t.string() }), + mixedUnion, + { + code: () => "hello", + }, + ), */ + }, + Policy.public(), + ); + }, +}).catch((err) => { + console.log(err); + throw err; +}); diff --git a/typegate/tests/metagen/typegraphs/sample/metatype.yml b/typegate/tests/metagen/typegraphs/sample/metatype.yml new file mode 100644 index 0000000000..de36d20f13 --- /dev/null +++ b/typegate/tests/metagen/typegraphs/sample/metatype.yml @@ -0,0 +1,20 @@ +typegates: + dev: + url: "http://localhost:7890" + username: admin + password: password + +metagen: + targets: + main: + - generator: client_ts + path: ./ts/ + typegraph_path: ../sample.ts + - generator: client_py + path: ./py/ + typegraph_path: ../sample.ts + - generator: client_rs + path: ./rs/ + typegraph_path: ../sample.ts + # skip_cargo_toml: true + skip_lib_rs: true diff --git a/typegate/tests/metagen/typegraphs/sample/py/client.py b/typegate/tests/metagen/typegraphs/sample/py/client.py new file mode 100644 index 0000000000..5c782652c7 --- /dev/null +++ b/typegate/tests/metagen/typegraphs/sample/py/client.py @@ -0,0 +1,701 @@ +# This file was @generated by metagen and is intended +# to be generated again on subsequent metagen runs. + +import typing +import dataclasses as dc +import json +import urllib.request as request +import urllib.error +import http.client as http_c + + +def selection_to_nodes( + selection: "SelectionErased", + metas: typing.Dict[str, typing.Callable[[], "NodeMeta"]], + parent_path: str, +) -> typing.List["SelectNode[typing.Any]"]: + out = [] + flags = selection.get("_") + if flags is not None and not isinstance(flags, SelectionFlags): + raise Exception( + f"selection field '_' should be of type SelectionFlags but found {type(flags)}" + ) + select_all = True if flags is not None and flags.select_all else False + found_nodes = set(selection.keys()) + for node_name, meta_fn in metas.items(): + found_nodes.discard(node_name) + + node_selection = selection.get(node_name) + if node_selection is False or (node_selection is None and not select_all): + # this node was not selected + continue + + meta = meta_fn() + + # we splat out any aliasing of nodes here + node_instances = ( + [(key, val) for key, val in node_selection.items.items()] + if isinstance(node_selection, Alias) + else [(node_name, node_selection)] + ) + + for instance_name, instance_selection in node_instances: + # print(parent_path, instance_selection, meta.sub_nodes, instance_selection, flags) + if instance_selection is False or ( + instance_selection is None and not select_all + ): + # this instance was not selected + continue + if isinstance(instance_selection, Alias): + raise Exception( + f"nested Alias node discovored at {parent_path}.{instance_name}" + ) + + instance_args: typing.Optional[NodeArgs] = None + if meta.arg_types is not None: + arg = instance_selection + + if isinstance(arg, tuple): + arg = arg[0] + + # arg types are always TypedDicts + if not isinstance(arg, dict): + raise Exception( + f"node at {parent_path}.{instance_name} is a node that " + + "requires arguments " + + f"but detected argument is typeof {type(arg)}" + ) + + # convert arg dict to NodeArgs + expected_args = {key: val for key, val in meta.arg_types.items()} + instance_args = {} + for key, val in arg.items(): + ty_name = expected_args.pop(key) + if ty_name is None: + raise Exception( + f"unexpected argument ${key} at {parent_path}.{instance_name}" + ) + instance_args[key] = NodeArgValue(ty_name, val) + + sub_nodes: typing.Optional[typing.List[SelectNode]] = None + if meta.sub_nodes is not None: + sub_selections = instance_selection + + # if node requires both selection and arg, it must be + # a CompositeSelectArgs which is a tuple selection + if meta.arg_types is not None: + if not isinstance(sub_selections, tuple): + raise Exception( + f"node at {parent_path}.{instance_name} is a composite " + + "that requires an argument object " + + f"but selection is typeof {type(sub_selections)}" + ) + sub_selections = sub_selections[1] + + elif isinstance(sub_selections, tuple): + raise Exception( + f"node at {parent_path}.{instance_name} " + + "is a composite that takes no arguments " + + f"but selection is typeof {type(instance_selection)}", + ) + + # selection types are always TypedDicts as well + if not isinstance(sub_selections, dict): + raise Exception( + f"node at {parent_path}.{instance_name} " + + "is a no argument composite but first element of " + + f"selection is typeof {type(instance_selection)}", + ) + sub_nodes = selection_to_nodes( + typing.cast("SelectionErased", sub_selections), + meta.sub_nodes, + f"{parent_path}.{instance_name}", + ) + + node = SelectNode(node_name, instance_name, instance_args, sub_nodes) + out.append(node) + + found_nodes.discard("_") + if len(found_nodes) > 0: + raise Exception( + f"unexpected nodes found in selection set at {parent_path}: {found_nodes}", + ) + return out + + +# +# --- --- Util types --- --- # +# + +Out = typing.TypeVar("Out", covariant=True) + +T = typing.TypeVar("T") + +ArgT = typing.TypeVar("ArgT", bound=typing.Mapping[str, typing.Any]) +SelectionT = typing.TypeVar("SelectionT") + + +# +# --- --- Graph node types --- --- # +# + + +@dc.dataclass +class SelectNode(typing.Generic[Out]): + node_name: str + instance_name: str + args: typing.Optional["NodeArgs"] + sub_nodes: typing.Optional[typing.List["SelectNode"]] + + +@dc.dataclass +class QueryNode(SelectNode[Out]): + pass + + +@dc.dataclass +class MutationNode(SelectNode[Out]): + pass + + +@dc.dataclass +class NodeMeta: + sub_nodes: typing.Optional[typing.Dict[str, typing.Callable[[], "NodeMeta"]]] = None + arg_types: typing.Optional[typing.Dict[str, str]] = None + + +# +# --- --- Argument types --- --- # +# + + +@dc.dataclass +class NodeArgValue: + type_name: str + value: typing.Any + + +NodeArgs = typing.Dict[str, NodeArgValue] + + +class PlaceholderValue(typing.Generic[T]): + def __init__(self, key: str): + self.key = key + + +PlaceholderArgs = typing.Dict[str, PlaceholderValue] + + +class PreparedArgs: + def get(self, key: str) -> PlaceholderValue: + return PlaceholderValue(key) + + +# +# --- --- Selection types --- --- # +# + + +class Alias(typing.Generic[SelectionT]): + """ + Request multiple instances of a single node under different + aliases. + """ + + def __init__(self, **aliases: SelectionT): + self.items = aliases + + +ScalarSelectNoArgs = typing.Union[bool, Alias[typing.Literal[True]], None] +ScalarSelectArgs = typing.Union[ + ArgT, + PlaceholderArgs, + Alias[typing.Union[ArgT, PlaceholderArgs]], + typing.Literal[False], + None, +] +CompositeSelectNoArgs = typing.Union[ + SelectionT, Alias[SelectionT], typing.Literal[False], None +] +CompositeSelectArgs = typing.Union[ + typing.Tuple[typing.Union[ArgT, PlaceholderArgs], SelectionT], + Alias[typing.Tuple[typing.Union[ArgT, PlaceholderArgs], SelectionT]], + typing.Literal[False], + None, +] + + +# FIXME: ideally this would be a TypedDict +# to allow full dict based queries but +# we need to reliably identify SelectionFlags at runtime +# but TypedDicts don't allow instanceof +@dc.dataclass +class SelectionFlags: + select_all: typing.Union[bool, None] = None + + +class Selection(typing.TypedDict, total=False): + _: SelectionFlags + + +SelectionErased = typing.Mapping[ + str, + typing.Union[ + SelectionFlags, + ScalarSelectNoArgs, + ScalarSelectArgs[typing.Mapping[str, typing.Any]], + CompositeSelectNoArgs["SelectionErased"], + # FIXME: should be possible to make SelectionT here `SelectionErased` recursively + # but something breaks + CompositeSelectArgs[typing.Mapping[str, typing.Any], typing.Any], + ], +] + +# +# --- --- GraphQL types --- --- # +# + + +@dc.dataclass +class GraphQLTransportOptions: + headers: typing.Dict[str, str] + + +@dc.dataclass +class GraphQLRequest: + addr: str + method: str + headers: typing.Dict[str, str] + body: bytes + + +@dc.dataclass +class GraphQLResponse: + req: GraphQLRequest + status: int + headers: typing.Dict[str, str] + body: bytes + + +def convert_query_node_gql( + node: SelectNode, + variables: typing.Dict[str, NodeArgValue], +): + out = ( + f"{node.instance_name}: {node.node_name}" + if node.instance_name != node.node_name + else node.node_name + ) + if node.args is not None: + arg_row = "" + for key, val in node.args.items(): + name = f"in{len(variables)}" + variables[name] = val + arg_row += f"{key}: ${name}, " + if len(arg_row): + out += f"({arg_row[:-2]})" + + if node.sub_nodes is not None: + sub_node_list = "" + for node in node.sub_nodes: + sub_node_list += f"{convert_query_node_gql(node, variables)} " + out += f" {{ {sub_node_list}}}" + return out + + +class GraphQLTransportBase: + def __init__( + self, + addr: str, + opts: GraphQLTransportOptions, + ty_to_gql_ty_map: typing.Dict[str, str], + ): + self.addr = addr + self.opts = opts + self.ty_to_gql_ty_map = ty_to_gql_ty_map + + def build_gql( + self, + query: typing.Mapping[str, SelectNode], + ty: typing.Union[typing.Literal["query"], typing.Literal["mutation"]], + name: str = "", + ): + variables: typing.Dict[str, NodeArgValue] = {} + root_nodes = "" + for key, node in query.items(): + fixed_node = SelectNode(node.node_name, key, node.args, node.sub_nodes) + root_nodes += f" {convert_query_node_gql(fixed_node, variables)}\n" + args_row = "" + for key, val in variables.items(): + args_row += f"${key}: {self.ty_to_gql_ty_map[val.type_name]}, " + + if len(args_row): + args_row = f"({args_row[:-2]})" + + doc = f"{ty} {name}{args_row} {{\n{root_nodes}}}" + return (doc, {key: val.value for key, val in variables.items()}) + + def build_req( + self, + doc: str, + variables: typing.Dict[str, typing.Any], + opts: typing.Optional[GraphQLTransportOptions] = None, + ): + headers = {} + headers.update(self.opts.headers) + if opts: + headers.update(opts.headers) + headers.update( + { + "accept": "application/json", + "content-type": "application/json", + } + ) + data = json.dumps({"query": doc, "variables": variables}).encode("utf-8") + return GraphQLRequest( + addr=self.addr, + method="POST", + headers=headers, + body=data, + ) + + def handle_response(self, res: GraphQLResponse): + if res.status != 200: + raise Exception(f"graphql request failed with status {res.status}", res) + if res.headers.get("content-type") != "application/json": + raise Exception("unexpected content-type in graphql response", res) + parsed = json.loads(res.body) + if parsed.get("errors"): + raise Exception("graphql errors in response", parsed) + return parsed["data"] + + +class GraphQLTransportUrlib(GraphQLTransportBase): + def fetch( + self, + doc: str, + variables: typing.Dict[str, typing.Any], + opts: typing.Optional[GraphQLTransportOptions], + ): + req = self.build_req(doc, variables, opts) + try: + with request.urlopen( + request.Request( + url=req.addr, method=req.method, headers=req.headers, data=req.body + ) + ) as res: + http_res: http_c.HTTPResponse = res + return self.handle_response( + GraphQLResponse( + req, + status=http_res.status, + body=http_res.read(), + headers={key: val for key, val in http_res.headers.items()}, + ) + ) + except request.HTTPError as res: + return self.handle_response( + GraphQLResponse( + req, + status=res.status or 599, + body=res.read(), + headers={key: val for key, val in res.headers.items()}, + ) + ) + except urllib.error.URLError as err: + raise Exception(f"URL error: {err.reason}") + + def query( + self, + inp: typing.Dict[str, QueryNode[Out]], + opts: typing.Optional[GraphQLTransportOptions] = None, + name: str = "", + ) -> typing.Dict[str, Out]: + doc, variables = self.build_gql( + {key: val for key, val in inp.items()}, "query", name + ) + # print(doc,variables) + # return {} + return self.fetch(doc, variables, opts) + + def mutation( + self, + inp: typing.Dict[str, MutationNode[Out]], + opts: typing.Optional[GraphQLTransportOptions] = None, + name: str = "", + ) -> typing.Dict[str, Out]: + doc, variables = self.build_gql( + {key: val for key, val in inp.items()}, "mutation", name + ) + return self.fetch(doc, variables, opts) + + def prepare_query( + self, + fun: typing.Callable[[PreparedArgs], typing.Dict[str, QueryNode[Out]]], + name: str = "", + ) -> "PreparedRequestUrlib[Out]": + return PreparedRequestUrlib(self, fun, "query", name) + + def prepare_mutation( + self, + fun: typing.Callable[[PreparedArgs], typing.Dict[str, MutationNode[Out]]], + name: str = "", + ) -> "PreparedRequestUrlib[Out]": + return PreparedRequestUrlib(self, fun, "mutation", name) + + +class PreparedRequestBase(typing.Generic[Out]): + def __init__( + self, + transport: GraphQLTransportBase, + fun: typing.Callable[[PreparedArgs], typing.Mapping[str, SelectNode[Out]]], + ty: typing.Union[typing.Literal["query"], typing.Literal["mutation"]], + name: str = "", + ): + dry_run_node = fun(PreparedArgs()) + doc, variables = transport.build_gql(dry_run_node, ty, name) + self.doc = doc + self._mapping = variables + self.transport = transport + + def resolve_vars( + self, + args: typing.Mapping[str, typing.Any], + mappings: typing.Dict[str, typing.Any], + ): + resolved: typing.Dict[str, typing.Any] = {} + for key, val in mappings.items(): + if isinstance(val, PlaceholderValue): + resolved[key] = args[val.key] + elif isinstance(val, dict): + self.resolve_vars(args, val) + else: + resolved[key] = val + return resolved + + +class PreparedRequestUrlib(PreparedRequestBase[Out]): + def __init__( + self, + transport: GraphQLTransportUrlib, + fun: typing.Callable[[PreparedArgs], typing.Mapping[str, SelectNode[Out]]], + ty: typing.Union[typing.Literal["query"], typing.Literal["mutation"]], + name: str = "", + ): + super().__init__(transport, fun, ty, name) + self.transport = transport + + def perform( + self, + args: typing.Mapping[str, typing.Any], + opts: typing.Optional[GraphQLTransportOptions] = None, + ) -> typing.Dict[str, Out]: + resolved_vars = self.resolve_vars(args, self._mapping) + return self.transport.fetch(self.doc, resolved_vars, opts) + + +# +# --- --- QueryGraph types --- --- # +# + + +class QueryGraphBase: + def __init__(self, ty_to_gql_ty_map: typing.Dict[str, str]): + self.ty_to_gql_ty_map = ty_to_gql_ty_map + + def graphql_sync( + self, addr: str, opts: typing.Optional[GraphQLTransportOptions] = None + ): + return GraphQLTransportUrlib( + addr, opts or GraphQLTransportOptions({}), self.ty_to_gql_ty_map + ) + + +# +# --- --- Typegraph types --- --- # +# + + +class NodeDescs: + @staticmethod + def scalar(): + return NodeMeta() + + @staticmethod + def Post(): + return NodeMeta( + sub_nodes={ + "id": NodeDescs.scalar, + "slug": NodeDescs.scalar, + "title": NodeDescs.scalar, + }, + ) + + @staticmethod + def User(): + return NodeMeta( + sub_nodes={ + "id": NodeDescs.scalar, + "email": NodeDescs.scalar, + "posts": NodeDescs.Post, + }, + ) + + @staticmethod + def Func23(): + return NodeMeta( + sub_nodes=NodeDescs.User().sub_nodes, + ) + + @staticmethod + def Func27(): + return NodeMeta( + sub_nodes=NodeDescs.Post().sub_nodes, + ) + + @staticmethod + def Func28(): + return NodeMeta( + sub_nodes=NodeDescs.Post().sub_nodes, + arg_types={ + "id": "String13", + }, + ) + + @staticmethod + def Func26(): + return NodeMeta( + sub_nodes=NodeDescs.scalar().sub_nodes, + arg_types={ + "id": "String4", + "slug": "String1", + "title": "String1", + }, + ) + + @staticmethod + def Func25(): + return NodeMeta( + sub_nodes=NodeDescs.scalar().sub_nodes, + ) + + @staticmethod + def Func24(): + return NodeMeta( + sub_nodes=NodeDescs.Post().sub_nodes, + ) + + +StringUuid4 = str + +Post = typing.TypedDict( + "Post", + { + "id": StringUuid4, + "slug": str, + "title": str, + }, + total=False, +) + +Object21 = typing.TypedDict( + "Object21", + { + "id": str, + }, + total=False, +) + +StringEmail5 = str + +Post7 = typing.List[Post] + +User = typing.TypedDict( + "User", + { + "id": StringUuid4, + "email": StringEmail5, + "posts": Post7, + }, + total=False, +) + + +PostSelections = typing.TypedDict( + "PostSelections", + { + "_": SelectionFlags, + "id": ScalarSelectNoArgs, + "slug": ScalarSelectNoArgs, + "title": ScalarSelectNoArgs, + }, + total=False, +) + +UserSelections = typing.TypedDict( + "UserSelections", + { + "_": SelectionFlags, + "id": ScalarSelectNoArgs, + "email": ScalarSelectNoArgs, + "posts": CompositeSelectNoArgs["PostSelections"], + }, + total=False, +) + + +class QueryGraph(QueryGraphBase): + def __init__(self): + super().__init__( + { + "String4": "ID!", + "String1": "String!", + "String13": "String!", + } + ) + + def get_user(self, select: UserSelections) -> QueryNode[User]: + node = selection_to_nodes( + {"getUser": select}, {"getUser": NodeDescs.Func23}, "$q" + )[0] + return QueryNode(node.node_name, node.instance_name, node.args, node.sub_nodes) + + def get_posts(self, select: PostSelections) -> QueryNode[Post]: + node = selection_to_nodes( + {"getPosts": select}, {"getPosts": NodeDescs.Func24}, "$q" + )[0] + return QueryNode(node.node_name, node.instance_name, node.args, node.sub_nodes) + + def scalar_no_args(self) -> QueryNode[str]: + node = selection_to_nodes( + {"scalarNoArgs": True}, {"scalarNoArgs": NodeDescs.Func25}, "$q" + )[0] + return QueryNode(node.node_name, node.instance_name, node.args, node.sub_nodes) + + def scalar_args( + self, args: typing.Union[Post, PlaceholderArgs] + ) -> MutationNode[str]: + node = selection_to_nodes( + {"scalarArgs": args}, {"scalarArgs": NodeDescs.Func26}, "$q" + )[0] + return MutationNode( + node.node_name, node.instance_name, node.args, node.sub_nodes + ) + + def composite_no_args(self, select: PostSelections) -> MutationNode[Post]: + node = selection_to_nodes( + {"compositeNoArgs": select}, {"compositeNoArgs": NodeDescs.Func27}, "$q" + )[0] + return MutationNode( + node.node_name, node.instance_name, node.args, node.sub_nodes + ) + + def composite_args( + self, args: typing.Union[Object21, PlaceholderArgs], select: PostSelections + ) -> MutationNode[Post]: + node = selection_to_nodes( + {"compositeArgs": (args, select)}, {"compositeArgs": NodeDescs.Func28}, "$q" + )[0] + return MutationNode( + node.node_name, node.instance_name, node.args, node.sub_nodes + ) diff --git a/typegate/tests/metagen/typegraphs/sample/py/main.py b/typegate/tests/metagen/typegraphs/sample/py/main.py new file mode 100644 index 0000000000..084f56a775 --- /dev/null +++ b/typegate/tests/metagen/typegraphs/sample/py/main.py @@ -0,0 +1,110 @@ +from client import ( + QueryGraph, + PostSelections, + SelectionFlags, + UserSelections, + Alias, +) +import json +import os + +qg = QueryGraph() +port = os.getenv("TG_PORT") +gql_client = qg.graphql_sync(f"http://localhost:{port}/sample") + +prepared_q = gql_client.prepare_query( + lambda args: { + "user": qg.get_user( + UserSelections( + _=SelectionFlags(select_all=True), + posts=Alias( + post1=PostSelections( + id=True, + slug=True, + title=True, + ), + post2=PostSelections( + _=SelectionFlags(select_all=True), + id=False, + ), + ), + ), + ), + "posts": qg.get_posts({"_": SelectionFlags(select_all=True)}), + "scalarNoArgs": qg.scalar_no_args(), + }, +) + +prepared_m = gql_client.prepare_mutation( + lambda args: { + "scalarArgs": qg.scalar_args( + { + "id": args.get("id"), + "slug": args.get("slug"), + "title": args.get("title"), + } + ), + "compositeNoArgs": qg.composite_no_args({"_": SelectionFlags(select_all=True)}), + "compositeArgs": qg.composite_args( + { + "id": args.get("id"), + }, + {"_": SelectionFlags(select_all=True)}, + ), + }, +) + +res1 = prepared_q.perform({}) +res1a = prepared_q.perform({}) + +res2 = prepared_m.perform( + { + "id": "94be5420-8c4a-4e67-b4f4-e1b2b54832a2", + "slug": "s", + "title": "t", + } +) + +res3 = gql_client.query( + { + "user": qg.get_user( + UserSelections( + _=SelectionFlags(select_all=True), + posts=Alias( + post1=PostSelections( + id=True, + slug=True, + title=True, + ), + post2=PostSelections( + _=SelectionFlags(select_all=True), + id=False, + ), + ), + ), + ), + "posts": qg.get_posts({"_": SelectionFlags(select_all=True)}), + "scalarNoArgs": qg.scalar_no_args(), + } +) + +res4 = gql_client.mutation( + { + "scalarArgs": qg.scalar_args( + { + "id": "94be5420-8c4a-4e67-b4f4-e1b2b54832a2", + "slug": "", + "title": "", + } + ), + "compositeNoArgs": qg.composite_no_args({"_": SelectionFlags(select_all=True)}), + "compositeArgs": qg.composite_args( + { + "id": "94be5420-8c4a-4e67-b4f4-e1b2b54832a2", + }, + {"_": SelectionFlags(select_all=True)}, + ), + } +) + +print(json.dumps([res1, res1a, res2, res3, res4])) diff --git a/typegate/tests/metagen/typegraphs/sample/rs/Cargo.lock b/typegate/tests/metagen/typegraphs/sample/rs/Cargo.lock new file mode 100644 index 0000000000..34a50f6ca2 --- /dev/null +++ b/typegate/tests/metagen/typegraphs/sample/rs/Cargo.lock @@ -0,0 +1,1318 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" + +[[package]] +name = "cc" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] +name = "h2" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" + +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "object" +version = "0.36.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "openssl" +version = "0.10.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "reqwest" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.23.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +dependencies = [ + "base64", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" + +[[package]] +name = "rustls-webpki" +version = "0.102.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "sample_mdk" +version = "0.0.1" +dependencies = [ + "reqwest", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.206" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b3e4cd94123dd520a128bcd11e34d9e9e423e7e3e50425cb1b4b1e3549d0284" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.206" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabfb6138d2383ea8208cf98ccf69cdfb1aff4088460681d84189aa259762f97" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.124" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66ad62847a56b3dba58cc891acd13884b9c61138d330c0d7b6181713d4fce38d" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.39.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/typegate/tests/metagen/typegraphs/sample/rs/Cargo.toml b/typegate/tests/metagen/typegraphs/sample/rs/Cargo.toml new file mode 100644 index 0000000000..336b0dd27c --- /dev/null +++ b/typegate/tests/metagen/typegraphs/sample/rs/Cargo.toml @@ -0,0 +1,15 @@ +package.name = "sample_mdk" +package.edition = "2021" +package.version = "0.0.1" + +[dependencies] +serde = { version = "1.0.203", features = ["derive"] } +serde_json = "1.0.117" +reqwest = { version = "0.12", features = ["blocking","json"] } +tokio = { version = "1", features = ["rt-multi-thread"] } + +# The options after here are configured for crates intended to be +# wasm artifacts. Remove them if your usage is different +[[bin]] +name = "sample_mdk" +path = "main.rs" diff --git a/typegate/tests/metagen/typegraphs/sample/rs/client.rs b/typegate/tests/metagen/typegraphs/sample/rs/client.rs new file mode 100644 index 0000000000..37a3700395 --- /dev/null +++ b/typegate/tests/metagen/typegraphs/sample/rs/client.rs @@ -0,0 +1,2318 @@ +// This file was @generated by metagen and is intended +// to be generated again on subsequent metagen runs. + +// Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0. +// SPDX-License-Identifier: MPL-2.0 + +use std::{collections::HashMap, marker::PhantomData}; + +use reqwest::Url; +use serde::{Deserialize, Serialize}; + +pub type CowStr = std::borrow::Cow<'static, str>; +pub type BoxErr = Box; +pub type JsonObject = serde_json::Map; + +fn to_json_value(val: T) -> serde_json::Value { + serde_json::to_value(val).expect("error serializing value") +} + +/// Build the SelectNodeErased tree from the SelectionErasedMap tree +/// according to the NodeMeta tree. In this function +/// - arguments are associated with their types +/// - aliases get splatted into the node tree +/// - light query validation takes place +fn selection_to_node_set( + selection: SelectionErasedMap, + metas: &HashMap, + parent_path: String, +) -> Result, SelectionError> { + let mut out = vec![]; + let mut selection = selection.0; + let mut found_nodes = selection + .keys() + .cloned() + .collect::>(); + for (node_name, meta_fn) in metas.iter() { + found_nodes.remove(&node_name[..]); + + let Some(node_selection) = selection.remove(&node_name[..]) else { + // this node was not selected + continue; + }; + + let node_instances = match node_selection { + SelectionErased::None => continue, + SelectionErased::Scalar => vec![(node_name.clone(), NodeArgsErased::None, None)], + SelectionErased::ScalarArgs(args) => { + vec![(node_name.clone(), args, None)] + } + SelectionErased::Composite(select) => { + vec![(node_name.clone(), NodeArgsErased::None, Some(select))] + } + SelectionErased::CompositeArgs(args, select) => { + vec![(node_name.clone(), args, Some(select))] + } + SelectionErased::Alias(aliases) => aliases + .into_iter() + .map(|(instance_name, selection)| { + let (args, select) = match selection { + AliasSelection::Scalar => (NodeArgsErased::None, None), + AliasSelection::ScalarArgs(args) => (args, None), + AliasSelection::Composite(select) => (NodeArgsErased::None, Some(select)), + AliasSelection::CompositeArgs(args, select) => (args, Some(select)), + }; + (instance_name, args, select) + }) + .collect(), + }; + + let meta = meta_fn(); + for (instance_name, args, select) in node_instances { + let args = if let Some(arg_types) = &meta.arg_types { + match args { + NodeArgsErased::Inline(args) => { + let instance_args = check_node_args(args, arg_types).map_err(|name| { + SelectionError::UnexpectedArgs { + name, + path: format!("{parent_path}.{instance_name}"), + } + })?; + Some(NodeArgsMerged::Inline(instance_args)) + } + NodeArgsErased::Placeholder(ph) => Some(NodeArgsMerged::Placeholder { + value: ph, + // FIXME: this clone can be improved + arg_types: arg_types.clone(), + }), + NodeArgsErased::None => { + return Err(SelectionError::MissingArgs { + path: format!("{parent_path}.{instance_name}"), + }) + } + } + } else { + None + }; + let sub_nodes = match (&meta.variants, &meta.sub_nodes) { + (Some(_), Some(_)) => unreachable!("union/either types can't have sub_nodes"), + (None, None) => SubNodes::None, + (variants, sub_nodes) => { + let Some(select) = select else { + return Err(SelectionError::MissingSubNodes { + path: format!("{parent_path}.{instance_name}"), + }); + }; + match select { + CompositeSelection::Atomic(select) => { + let Some(sub_nodes) = sub_nodes else { + return Err(SelectionError::UnexpectedUnion { + path: format!("{parent_path}.{instance_name}"), + }); + }; + SubNodes::Atomic(selection_to_node_set( + select, + sub_nodes, + format!("{parent_path}.{instance_name}"), + )?) + } + CompositeSelection::Union(variant_select) => { + let Some(variants) = variants else { + return Err(SelectionError::MissingUnion { + path: format!("{parent_path}.{instance_name}"), + }); + }; + let mut out = HashMap::new(); + for (variant_ty, select) in variant_select { + let Some(variant_meta) = variants.get(&variant_ty[..]) else { + return Err(SelectionError::UnexpectedVariant { + path: format!("{parent_path}.{instance_name}"), + varaint_ty: variant_ty.clone(), + }); + }; + let variant_meta = variant_meta(); + // this union member is a scalar + let Some(sub_nodes) = variant_meta.sub_nodes else { + continue; + }; + let nodes = selection_to_node_set( + select, + &sub_nodes, + format!("{parent_path}.{instance_name}"), + )?; + out.insert(variant_ty, nodes); + } + SubNodes::Union(out) + } + } + } + }; + + out.push(SelectNodeErased { + node_name: node_name.clone(), + instance_name, + args, + sub_nodes, + }) + } + } + Ok(out) +} + +#[derive(Debug)] +pub enum SelectionError { + MissingArgs { path: String }, + MissingSubNodes { path: String }, + MissingUnion { path: String }, + UnexpectedArgs { path: String, name: String }, + UnexpectedUnion { path: String }, + UnexpectedVariant { path: String, varaint_ty: CowStr }, +} + +impl std::fmt::Display for SelectionError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SelectionError::MissingArgs { path } => write!(f, "args are missing at node {path}"), + SelectionError::UnexpectedArgs { path, name } => { + write!(f, "unexpected arg '${name}' at node {path}") + } + SelectionError::MissingSubNodes { path } => { + write!(f, "node at {path} is a composite but no selection found") + } + SelectionError::MissingUnion { path } => write!( + f, + "node at {path} is a union but provided selection is atomic" + ), + SelectionError::UnexpectedUnion { path } => write!( + f, + "node at {path} is an atomic type but union selection provided" + ), + SelectionError::UnexpectedVariant { path, varaint_ty } => { + write!(f, "node at {path} has no variant called '{varaint_ty}'") + } + } + } +} +impl std::error::Error for SelectionError {} + +// +// --- --- Graph node types --- --- // +// + +type NodeMetaFn = fn() -> NodeMeta; + +/// How the [`node_metas`] module encodes the description +/// of the typegraph. +struct NodeMeta { + sub_nodes: Option>, + arg_types: Option>, + variants: Option>, +} + +enum SubNodes { + None, + Atomic(Vec), + Union(HashMap>), +} + +/// The final form of the nodes used in queries. +pub struct SelectNodeErased { + node_name: CowStr, + instance_name: CowStr, + args: Option, + sub_nodes: SubNodes, +} + +/// Wrappers around [`SelectNodeErased`] that only holds query nodes +pub struct QueryNode(SelectNodeErased, PhantomData<(Out,)>); +/// Wrappers around [`SelectNodeErased`] that only holds mutation nodes +pub struct MutationNode(SelectNodeErased, PhantomData<(Out,)>); + +/* /// Trait used to track the `Out` type parameter for [`QueryNode`]/[`MutationNode`] +pub trait ToSelectNode { + type Out; + + fn erased(self) -> SelectNodeErased; +} */ + +/// A variation of [`ToSelectNode`] to only be implemented +/// by aggregates of select nodes like [Vec]s. +pub trait ToSelectDoc { + type Out; + + fn to_select_doc(self) -> Vec; + fn parse_response(data: Vec) -> Result; +} + +/// Marker trait for [`ToSelectDoc`] implementors that only carry query nodes. +pub trait ToQueryDoc {} +/// Marker trait for [`ToSelectDoc`] implementors that only carry mutation nodes. +pub trait ToMutationDoc {} + +/// Struct used to mark query associated types that are generic about effect. +pub struct QueryMarker; +/// Struct used to mark mutationo associated types that are generic about effect. +pub struct MutationMarker; + +/// A node that's yet to have it's subnodes specified. +/// Use [`select`][Self::select] and [`select_aliased`][Self::select_aliased] +/// to finalize it. +/// [`select_aliased`][Self::select_aliased] will allow you to use [`alias`] +/// nodes but the returned object will be a raw [`serde_json::Value`]. +/// This type is generic over effect using the `QTy` parameter. +pub struct UnselectedNode { + root_name: CowStr, + root_meta: NodeMetaFn, + args: NodeArgsErased, + _marker: PhantomData<(SelT, SelAliasedT, QTy, Out)>, +} + +impl UnselectedNode +where + SelT: Into, +{ + fn select_erased(self, select: SelT) -> Result { + let nodes = selection_to_node_set( + SelectionErasedMap( + [( + self.root_name.clone(), + match self.args { + NodeArgsErased::None => SelectionErased::Composite(select.into()), + args => SelectionErased::CompositeArgs(args, select.into()), + }, + )] + .into(), + ), + &[(self.root_name, self.root_meta)].into(), + "$q".into(), + )?; + Ok(nodes.into_iter().next().unwrap()) + } +} + +impl UnselectedNode +where + SelAliased: Into, +{ + fn select_aliased_erased(self, select: SelAliased) -> Result { + let nodes = selection_to_node_set( + SelectionErasedMap( + [( + self.root_name.clone(), + match self.args { + NodeArgsErased::None => SelectionErased::Composite(select.into()), + args => SelectionErased::CompositeArgs(args, select.into()), + }, + )] + .into(), + ), + &[(self.root_name, self.root_meta)].into(), + "$q".into(), + )?; + Ok(nodes.into_iter().next().unwrap()) + } +} + +// NOTE: we'll need a select method implementation for each ATy x QTy pair + +impl UnselectedNode +where + SelT: Into, +{ + pub fn select(self, select: SelT) -> Result, SelectionError> { + Ok(QueryNode(self.select_erased(select)?, PhantomData)) + } +} +impl UnselectedNode +where + SelAliased: Into, +{ + pub fn select_aliased( + self, + select: SelAliased, + ) -> Result, SelectionError> { + Ok(QueryNode(self.select_aliased_erased(select)?, PhantomData)) + } +} +impl UnselectedNode +where + SelT: Into, +{ + pub fn select(self, select: SelT) -> Result, SelectionError> { + Ok(MutationNode(self.select_erased(select)?, PhantomData)) + } +} +impl UnselectedNode +where + SelAliased: Into, +{ + pub fn select_aliased( + self, + select: SelAliased, + ) -> Result, SelectionError> { + Ok(MutationNode( + self.select_aliased_erased(select)?, + PhantomData, + )) + } +} + +// --- --- Impl ToSelectDoc --- --- /// + +impl ToSelectDoc for QueryNode +where + Out: serde::de::DeserializeOwned, +{ + type Out = Out; + + fn to_select_doc(self) -> Vec { + vec![self.0] + } + + fn parse_response(data: Vec) -> Result { + let mut data = data.into_iter(); + serde_json::from_value(data.next().unwrap()) + } +} +impl ToQueryDoc for QueryNode {} +impl ToSelectDoc for MutationNode +where + Out: serde::de::DeserializeOwned, +{ + type Out = Out; + + fn to_select_doc(self) -> Vec { + vec![self.0] + } + + fn parse_response(data: Vec) -> Result { + let mut data = data.into_iter(); + serde_json::from_value(data.next().unwrap()) + } +} +impl ToMutationDoc for MutationNode {} + +#[macro_export] +macro_rules! impl_for_tuple { + ($($idx:tt $ty:tt),+) => { + impl<$($ty,)+> ToSelectDoc for ($(QueryNode<$ty>,)+) + where $($ty: serde::de::DeserializeOwned,)+ + { + type Out = ($($ty,)+); + + fn to_select_doc(self) -> Vec { + vec![ + $(self.$idx.0,)+ + ] + } + fn parse_response(data: Vec) -> Result { + let mut data = data.into_iter(); + let mut next = move |_idx| data.next().unwrap(); + Ok(( + + $(serde_json::from_value(next($idx))?,)+ + )) + } + } + impl<$($ty,)+> ToSelectDoc for ($(MutationNode<$ty>,)+) + where $($ty: serde::de::DeserializeOwned,)+ + { + type Out = ($($ty,)+); + + fn to_select_doc(self) -> Vec { + vec![ + $(self.$idx.0,)+ + ] + } + fn parse_response(data: Vec) -> Result { + let mut data = data.into_iter(); + let mut next = move |_idx| data.next().unwrap(); + Ok(( + + $(serde_json::from_value(next($idx))?,)+ + )) + } + } + + impl<$($ty,)+> ToQueryDoc for ($($ty,)+) + where + $($ty: ToQueryDoc,)+ + {} + + impl<$($ty,)+> ToMutationDoc for ($($ty,)+) + where + $($ty: ToMutationDoc,)+ + {} + }; +} + +impl_for_tuple!(0 N0); +impl_for_tuple!(0 N0, 1 N1); +impl_for_tuple!(0 N0, 1 N1, 2 N2); +impl_for_tuple!(0 N0, 1 N1, 2 N2, 3 N3); +impl_for_tuple!(0 N0, 1 N1, 2 N2, 3 N3, 4 N4); +impl_for_tuple!(0 N0, 1 N1, 2 N2, 3 N3, 4 N4, 5 N5); +impl_for_tuple!(0 N0, 1 N1, 2 N2, 3 N3, 4 N4, 5 N5, 6 N6); +impl_for_tuple!(0 N0, 1 N1, 2 N2, 3 N3, 4 N4, 5 N5, 6 N6, 7 N7); +impl_for_tuple!(0 N0, 1 N1, 2 N2, 3 N3, 4 N4, 5 N5, 6 N6, 7 N7, 8 N8); +impl_for_tuple!(0 N0, 1 N1, 2 N2, 3 N3, 4 N4, 5 N5, 6 N6, 7 N7, 8 N8, 9 N9); +impl_for_tuple!(0 N0, 1 N1, 2 N2, 3 N3, 4 N4, 5 N5, 6 N6, 7 N7, 8 N8, 9 N9, 10 N10); +impl_for_tuple!(0 N0, 1 N1, 2 N2, 3 N3, 4 N4, 5 N5, 6 N6, 7 N7, 8 N8, 9 N9, 10 N10, 11 N11); + +// +// --- -- --- Selection types --- --- // +// + +// This is a newtype for Into trait impl purposes +#[derive(Debug)] +pub struct SelectionErasedMap(HashMap); + +#[derive(Debug)] +pub enum CompositeSelection { + Atomic(SelectionErasedMap), + Union(HashMap), +} + +impl Default for CompositeSelection { + fn default() -> Self { + CompositeSelection::Atomic(SelectionErasedMap(Default::default())) + } +} + +#[derive(Debug)] +enum SelectionErased { + None, + Scalar, + ScalarArgs(NodeArgsErased), + Composite(CompositeSelection), + CompositeArgs(NodeArgsErased, CompositeSelection), + Alias(HashMap), +} + +#[derive(Debug)] +pub enum AliasSelection { + Scalar, + ScalarArgs(NodeArgsErased), + Composite(CompositeSelection), + CompositeArgs(NodeArgsErased, CompositeSelection), +} + +#[derive(Default, Clone, Copy, Debug)] +pub struct HasAlias; +#[derive(Default, Clone, Copy, Debug)] +pub struct NoAlias; + +#[derive(Debug)] +pub struct AliasInfo { + aliases: HashMap, + _phantom: PhantomData<(ArgT, SelT, ATyag)>, +} + +#[derive(Debug)] +pub enum ScalarSelect { + Get, + Skip, + Alias(AliasInfo<(), (), ATy>), +} +#[derive(Debug)] +pub enum ScalarSelectArgs { + Get(NodeArgsErased, PhantomData), + Skip, + Alias(AliasInfo), +} +#[derive(Debug)] +pub enum CompositeSelect { + Get(CompositeSelection, PhantomData), + Skip, + Alias(AliasInfo<(), SelT, ATy>), +} +#[derive(Debug)] +pub enum CompositeSelectArgs { + Get( + NodeArgsErased, + CompositeSelection, + PhantomData<(ArgT, SelT)>, + ), + Skip, + Alias(AliasInfo), +} + +pub struct Get; +pub struct Skip; +pub struct Args(ArgT); +pub struct Select(SelT); +pub struct ArgSelect(ArgT, SelT); +pub struct Alias(AliasInfo); + +/// Shorthand for `Default::default`. All selections generally default +/// to [`skip`]. +pub fn default() -> T { + T::default() +} +/// Include all sub nodes excpet those that require arguments +pub fn all() -> T { + T::all() +} +/// Select the node for inclusion. +pub fn get>() -> T { + T::from(Get) +} +/// Skip this node when queryig. +pub fn skip>() -> T { + T::from(Skip) +} +/// Provide argumentns for a scalar node. +pub fn args>>(args: ArgT) -> T { + T::from(Args(args)) +} +/// Provide selections for a composite node that takes no args. +pub fn select>>(selection: SelT) -> T { + T::from(Select(selection)) +} +/// Provide arguments and selections for a composite node. +pub fn arg_select>>(args: ArgT, selection: SelT) -> T { + T::from(ArgSelect(args, selection)) +} + +/// Query the same node multiple times using aliases. +/// +/// WARNING: make sure your alias names don't clash across sibling +/// nodes. +pub fn alias(info: impl Into>) -> T +where + S: Into, + ASelT: Into, + T: From> + FromAliasSelection, +{ + let info: HashMap<_, _> = info.into(); + T::from(Alias(AliasInfo { + aliases: info + .into_iter() + .map(|(name, sel)| (name.into(), sel.into())) + .collect(), + _phantom: PhantomData, + })) +} + +pub trait Selection { + /// Include all sub nodes excpet those that require arguments + fn all() -> Self; +} + +// --- Impl SelectionType impls --- // + +impl Selection for ScalarSelect { + fn all() -> Self { + Self::Get + } +} +impl Selection for ScalarSelectArgs { + fn all() -> Self { + Self::Skip + } +} +impl Selection for CompositeSelect +where + SelT: Selection + Into, +{ + fn all() -> Self { + let sel = SelT::all(); + Self::Get(sel.into(), PhantomData) + } +} +impl Selection for CompositeSelectArgs +where + SelT: Selection, +{ + fn all() -> Self { + Self::Skip + } +} +// --- Default impls --- // + +impl Default for ScalarSelect { + fn default() -> Self { + Self::Skip + } +} +impl Default for ScalarSelectArgs { + fn default() -> Self { + Self::Skip + } +} +impl Default for CompositeSelect { + fn default() -> Self { + Self::Skip + } +} +impl Default for CompositeSelectArgs { + fn default() -> Self { + Self::Skip + } +} + +// --- From Get/Skip...etc impls --- // + +impl From for ScalarSelect { + fn from(_: Get) -> Self { + Self::Get + } +} + +impl From for ScalarSelect { + fn from(_: Skip) -> Self { + Self::Skip + } +} +impl From for ScalarSelectArgs { + fn from(_: Skip) -> Self { + Self::Skip + } +} +impl From for CompositeSelect { + fn from(_: Skip) -> Self { + Self::Skip + } +} +impl From for CompositeSelectArgs { + fn from(_: Skip) -> Self { + Self::Skip + } +} + +impl From> for ScalarSelectArgs +where + ArgT: Serialize, +{ + fn from(Args(args): Args) -> Self { + Self::Get(NodeArgsErased::Inline(to_json_value(args)), PhantomData) + } +} + +impl From> for CompositeSelect +where + SelT: Into, +{ + fn from(Select(selection): Select) -> Self { + Self::Get(selection.into(), PhantomData) + } +} + +impl From> for CompositeSelectArgs +where + ArgT: Serialize, + SelT: Into, +{ + fn from(ArgSelect(args, selection): ArgSelect) -> Self { + Self::Get( + NodeArgsErased::Inline(to_json_value(args)), + selection.into(), + PhantomData, + ) + } +} + +impl From> for ScalarSelectArgs { + fn from(value: PlaceholderArg) -> Self { + Self::Get(NodeArgsErased::Placeholder(value.value), PhantomData) + } +} +impl From> + for CompositeSelectArgs +where + SelT: Into, +{ + fn from(value: PlaceholderArgSelect) -> Self { + Self::Get( + NodeArgsErased::Placeholder(value.value), + value.selection.into(), + PhantomData, + ) + } +} + +// --- ToAliasSelection impls --- // + +/// This is a marker trait that allows the core selection types +/// like CompositeSelectNoArgs to mark which types can be used +/// as their aliasing nodes. This prevents usage of invalid selections +/// on aliases like [`Skip`]. +pub trait FromAliasSelection {} + +impl FromAliasSelection for ScalarSelect {} +impl FromAliasSelection> for ScalarSelectArgs {} +impl FromAliasSelection> for CompositeSelect {} +impl FromAliasSelection> + for CompositeSelectArgs +{ +} + +// --- From Alias impls --- // + +impl From>> for ScalarSelect { + fn from(Alias(info): Alias<(), ScalarSelect>) -> Self { + Self::Alias(AliasInfo { + aliases: info.aliases, + _phantom: PhantomData, + }) + } +} +impl From> for ScalarSelectArgs { + fn from(Alias(info): Alias) -> Self { + Self::Alias(info) + } +} +impl From> for CompositeSelect { + fn from(Alias(info): Alias<(), SelT>) -> Self { + Self::Alias(info) + } +} +impl From> for CompositeSelectArgs { + fn from(Alias(info): Alias) -> Self { + Self::Alias(info) + } +} + +// --- Into SelectionErased impls --- // + +impl From> for SelectionErased { + fn from(value: AliasInfo) -> SelectionErased { + SelectionErased::Alias(value.aliases) + } +} + +impl From> for SelectionErased { + fn from(value: ScalarSelect) -> SelectionErased { + use ScalarSelect::*; + match value { + Get => SelectionErased::Scalar, + Skip => SelectionErased::None, + Alias(alias) => alias.into(), + } + } +} + +impl From> for SelectionErased { + fn from(value: ScalarSelectArgs) -> SelectionErased { + use ScalarSelectArgs::*; + match value { + Get(arg, _) => SelectionErased::ScalarArgs(arg), + Skip => SelectionErased::None, + Alias(alias) => alias.into(), + } + } +} + +impl From> for SelectionErased { + fn from(value: CompositeSelect) -> SelectionErased { + use CompositeSelect::*; + match value { + Get(selection, _) => SelectionErased::Composite(selection), + Skip => SelectionErased::None, + Alias(alias) => alias.into(), + } + } +} + +impl From> for SelectionErased +where + SelT: Into, +{ + fn from(value: CompositeSelectArgs) -> SelectionErased { + use CompositeSelectArgs::*; + match value { + Get(args, selection, _) => SelectionErased::CompositeArgs(args, selection), + Skip => SelectionErased::None, + Alias(alias) => alias.into(), + } + } +} + +// --- UnionMember impls --- // + +/// The following trait is used for types that implement +/// selections for the composite members of unions. +/// +/// The err return value indicates the case where +/// aliases are used selections on members which is an error +/// +/// This state is currently impossible to arrive at since +/// AliasInfo has no public construction methods with NoAlias +/// set. Union selection types make sure all their immediate +/// member selection use NoAlias to prevent this invalid stat.e +pub trait UnionMember { + fn composite(self) -> Option; +} + +/// Internal marker trait use to make sure we can't have union members +/// selection being another union selection. +trait NotUnionSelection {} + +// NOTE: UnionMembers are all NoAlias +impl UnionMember for ScalarSelect { + fn composite(self) -> Option { + None + } +} + +impl UnionMember for ScalarSelectArgs { + fn composite(self) -> Option { + None + } +} + +impl UnionMember for CompositeSelect +where + SelT: NotUnionSelection, +{ + fn composite(self) -> Option { + use CompositeSelect::*; + match self { + Get(CompositeSelection::Atomic(selection), _) => Some(selection), + Skip => None, + Get(CompositeSelection::Union(_), _) => { + unreachable!("union selection on union member selection. how??") + } + Alias(_) => unreachable!("alias discovored on union/either member. how??"), + } + } +} + +impl UnionMember for CompositeSelectArgs +where + SelT: NotUnionSelection, +{ + fn composite(self) -> Option { + use CompositeSelectArgs::*; + match self { + Get(_args, CompositeSelection::Atomic(selection), _) => Some(selection), + Skip => None, + Get(_args, CompositeSelection::Union(_), _) => { + unreachable!("union selection on union member selection. how??") + } + Alias(_) => unreachable!("alias discovored on union/either member. how??"), + } + } +} + +// --- Into AliasSelection impls --- // + +impl From for AliasSelection { + fn from(_val: Get) -> Self { + AliasSelection::Scalar + } +} +impl From> for AliasSelection +where + ArgT: Serialize, +{ + fn from(val: Args) -> Self { + AliasSelection::ScalarArgs(NodeArgsErased::Inline(to_json_value(val.0))) + } +} +impl From> for AliasSelection +where + SelT: Into, +{ + fn from(val: Select) -> Self { + let map = val.0.into(); + AliasSelection::Composite(map) + } +} + +impl From> for AliasSelection +where + ArgT: Serialize, + SelT: Into, +{ + fn from(val: ArgSelect) -> Self { + let map = val.1.into(); + AliasSelection::CompositeArgs(NodeArgsErased::Inline(to_json_value(val.0)), map) + } +} +impl From> for AliasSelection { + fn from(val: ScalarSelect) -> Self { + use ScalarSelect::*; + match val { + Get => AliasSelection::Scalar, + _ => unreachable!(), + } + } +} +impl From> for AliasSelection { + fn from(val: ScalarSelectArgs) -> Self { + use ScalarSelectArgs::*; + match val { + Get(args, _) => AliasSelection::ScalarArgs(args), + _ => unreachable!(), + } + } +} + +impl From> for AliasSelection +where + SelT: Into, +{ + fn from(val: CompositeSelect) -> Self { + use CompositeSelect::*; + match val { + Get(select, _) => AliasSelection::Composite(select), + _ => unreachable!(), + } + } +} +impl From> for AliasSelection +where + SelT: Into, +{ + fn from(val: CompositeSelectArgs) -> Self { + use CompositeSelectArgs::*; + match val { + Get(args, selection, _) => AliasSelection::CompositeArgs(args, selection), + _ => unreachable!(), + } + } +} + +// TODO: convert to proc_macro +#[macro_export] +macro_rules! impl_selection_traits { + ($ty:ident,$($field:tt),+) => { + impl From<$ty> for CompositeSelection { + fn from(value: $ty) -> CompositeSelection { + CompositeSelection::Atomic(SelectionErasedMap( + [ + $((stringify!($field).into(), value.$field.into()),)+ + ] + .into(), + )) + } + } + + impl Selection for $ty { + fn all() -> Self { + Self { + $($field: all(),)+ + } + } + } + + impl NotUnionSelection for $ty {} + }; +} +#[macro_export] +macro_rules! impl_union_selection_traits { + ($ty:ident,$(($variant_ty:tt, $field:tt)),+) => { + impl From<$ty> for CompositeSelection { + fn from(_value: $ty) -> CompositeSelection { + /*CompositeSelection::Union( + [ + $({ + let selection = + UnionMember::composite(value.$field); + selection.map(|val| ($variant_ty.into(), val)) + },)+ + ] + .into_iter() + .filter_map(|val| val) + .collect(), + )*/ + panic!("unions/either are wip") + } + } + }; +} + +// +// --- --- Argument types --- --- // +// + +pub enum NodeArgs { + Inline(ArgT), + Placeholder(PlaceholderValue), +} + +impl From for NodeArgs { + fn from(value: ArgT) -> Self { + Self::Inline(value) + } +} + +#[derive(Debug)] +pub enum NodeArgsErased { + None, + Inline(serde_json::Value), + Placeholder(PlaceholderValue), +} + +impl From> for NodeArgsErased +where + ArgT: Serialize, +{ + fn from(value: NodeArgs) -> Self { + match value { + NodeArgs::Inline(arg) => Self::Inline(to_json_value(arg)), + NodeArgs::Placeholder(ph) => Self::Placeholder(ph), + } + } +} + +enum NodeArgsMerged { + Inline(HashMap), + Placeholder { + value: PlaceholderValue, + arg_types: HashMap, + }, +} + +/// This checks the input arg json for a node +/// against the arg description from the [`NodeMeta`]. +fn check_node_args( + args: serde_json::Value, + arg_types: &HashMap, +) -> Result, String> { + let args = match args { + serde_json::Value::Object(val) => val, + _ => unreachable!(), + }; + let mut instance_args = HashMap::new(); + for (name, value) in args { + let Some(type_name) = arg_types.get(&name[..]) else { + return Err(name); + }; + instance_args.insert( + name.into(), + NodeArgValue { + type_name: type_name.clone(), + value, + }, + ); + } + Ok(instance_args) +} + +struct NodeArgValue { + type_name: CowStr, + value: serde_json::Value, +} + +pub struct PreparedArgs; + +impl PreparedArgs { + pub fn get(&mut self, key: impl Into, fun: F) -> NodeArgs + where + In: serde::de::DeserializeOwned, + F: Fn(In) -> ArgT + 'static + Send + Sync, + ArgT: Serialize, + { + NodeArgs::Placeholder(PlaceholderValue { + key: key.into(), + fun: Box::new(move |value| { + let value = serde_json::from_value(value)?; + let value = fun(value); + serde_json::to_value(value) + }), + }) + } + pub fn arg(&mut self, key: impl Into, fun: F) -> T + where + T: From>, + In: serde::de::DeserializeOwned, + F: Fn(In) -> ArgT + 'static + Send + Sync, + ArgT: Serialize, + { + T::from(PlaceholderArg { + value: PlaceholderValue { + key: key.into(), + fun: Box::new(move |value| { + let value = serde_json::from_value(value)?; + let value = fun(value); + serde_json::to_value(value) + }), + }, + _phantom: PhantomData, + }) + } + pub fn arg_select( + &mut self, + key: impl Into, + selection: SelT, + fun: F, + ) -> T + where + T: From>, + In: serde::de::DeserializeOwned, + F: Fn(In) -> ArgT + 'static + Send + Sync, + ArgT: Serialize, + { + T::from(PlaceholderArgSelect { + value: PlaceholderValue { + key: key.into(), + fun: Box::new(move |value| { + let value = serde_json::from_value(value)?; + let value = fun(value); + serde_json::to_value(value) + }), + }, + selection, + _phantom: PhantomData, + }) + } +} + +pub struct PlaceholderValue { + key: CowStr, + fun: Box< + dyn Fn(serde_json::Value) -> Result + Send + Sync, + >, +} + +impl std::fmt::Debug for PlaceholderValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("PlaceholderValue") + .field("key", &self.key) + .finish_non_exhaustive() + } +} + +pub struct PlaceholderArg { + value: PlaceholderValue, + _phantom: PhantomData, +} +pub struct PlaceholderArgSelect { + value: PlaceholderValue, + selection: SelT, + _phantom: PhantomData, +} + +pub struct PlaceholderArgs(Arg); + +// +// --- --- GraphQL types --- --- // +// + +use graphql::*; +pub mod graphql { + use std::sync::Arc; + + use super::*; + + pub(super) type TyToGqlTyMap = Arc>; + + #[derive(Default, Clone)] + pub struct GraphQlTransportOptions { + headers: reqwest::header::HeaderMap, + timeout: Option, + } + + // PlaceholderValue, fieldName -> gql_var_name + type FoundPlaceholders = Vec<(PlaceholderValue, HashMap)>; + + fn select_node_to_gql( + ty_to_gql_ty_map: &TyToGqlTyMap, + dest: &mut impl std::fmt::Write, + node: SelectNodeErased, + variable_types: &mut HashMap, + variables_object: &mut JsonObject, + placeholders: &mut FoundPlaceholders, + ) -> std::fmt::Result { + if node.instance_name != node.node_name { + write!(dest, "{}: {}", node.instance_name, node.node_name)?; + } else { + write!(dest, "{}", node.node_name)?; + } + if let Some(args) = node.args { + match args { + NodeArgsMerged::Inline(args) => { + if !args.is_empty() { + write!(dest, "(")?; + for (key, val) in args { + let name = format!("in{}", variable_types.len()); + write!(dest, "{key}: ${name}, ")?; + variables_object.insert(name.clone(), val.value); + variable_types.insert(name.into(), val.type_name); + } + write!(dest, ")")?; + } + } + NodeArgsMerged::Placeholder { value, arg_types } => { + if !arg_types.is_empty() { + write!(dest, "(")?; + let mut map = HashMap::new(); + for (key, type_name) in arg_types { + let name = format!("in{}", variable_types.len()); + write!(dest, "{key}: ${name}, ")?; + variable_types.insert(name.clone().into(), type_name); + map.insert(key, name.into()); + } + write!(dest, ")")?; + placeholders.push((value, map)); + } + } + } + } + match node.sub_nodes { + SubNodes::None => {} + SubNodes::Atomic(sub_nodes) => { + write!(dest, "{{ ")?; + for node in sub_nodes { + select_node_to_gql( + ty_to_gql_ty_map, + dest, + node, + variable_types, + variables_object, + placeholders, + )?; + write!(dest, " ")?; + } + write!(dest, " }}")?; + } + SubNodes::Union(variants) => { + write!(dest, "{{ ")?; + for (ty, sub_nodes) in variants { + let gql_ty = ty_to_gql_ty_map + .get(&ty[..]) + .expect("impossible: no GraphQL type equivalent found for variant type"); + let gql_ty = match gql_ty.strip_suffix('!') { + Some(val) => val, + None => &gql_ty[..], + }; + write!(dest, " ... on {gql_ty} {{ ")?; + for node in sub_nodes { + select_node_to_gql( + ty_to_gql_ty_map, + dest, + node, + variable_types, + variables_object, + placeholders, + )?; + write!(dest, " ")?; + } + write!(dest, " }}")?; + } + write!(dest, " }}")?; + } + } + Ok(()) + } + + fn build_gql_doc( + ty_to_gql_ty_map: &TyToGqlTyMap, + nodes: Vec, + ty: &'static str, + name: Option, + ) -> Result<(String, JsonObject, FoundPlaceholders), GraphQLRequestError> { + use std::fmt::Write; + let mut variables_types = HashMap::new(); + let mut variables_values = serde_json::Map::new(); + let mut root_nodes = String::new(); + let mut placeholders = vec![]; + for (idx, node) in nodes.into_iter().enumerate() { + let node = SelectNodeErased { + instance_name: format!("node{idx}").into(), + ..node + }; + write!(&mut root_nodes, " ").expect("error building to string"); + select_node_to_gql( + ty_to_gql_ty_map, + &mut root_nodes, + node, + &mut variables_types, + &mut variables_values, + &mut placeholders, + ) + .expect("error building to string"); + writeln!(&mut root_nodes).expect("error building to string"); + } + let mut args_row = String::new(); + if !variables_types.is_empty() { + write!(&mut args_row, "(").expect("error building to string"); + for (key, ty) in &variables_types { + let gql_ty = ty_to_gql_ty_map.get(&ty[..]).ok_or_else(|| { + GraphQLRequestError::InvalidQuery { + error: Box::from(format!("unknown typegraph type found: {}", ty)), + } + })?; + write!(&mut args_row, "${key}: {gql_ty}, ").expect("error building to string"); + } + write!(&mut args_row, ")").expect("error building to string"); + } + let name = name.unwrap_or_else(|| "".into()); + let doc = format!("{ty} {name}{args_row} {{\n{root_nodes}}}"); + Ok((doc, variables_values, placeholders)) + } + + struct GraphQLRequest { + addr: Url, + method: reqwest::Method, + headers: reqwest::header::HeaderMap, + body: serde_json::Value, + } + + fn build_gql_req( + addr: Url, + doc: &str, + variables: &JsonObject, + opts: &GraphQlTransportOptions, + ) -> GraphQLRequest { + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert( + reqwest::header::ACCEPT, + "application/json".try_into().unwrap(), + ); + headers.insert( + reqwest::header::CONTENT_TYPE, + "application/json".try_into().unwrap(), + ); + headers.extend(opts.headers.clone()); + // println!("{doc}, {variables:#?}"); + let body = serde_json::json!({ + "query": doc, + "variables": variables + }); + GraphQLRequest { + addr, + method: reqwest::Method::POST, + headers, + body, + } + } + + #[derive(Debug)] + pub struct GraphQLResponse { + pub status: reqwest::StatusCode, + pub headers: reqwest::header::HeaderMap, + pub body: JsonObject, + } + + fn handle_response( + response: GraphQLResponse, + nodes_len: usize, + ) -> Result, GraphQLRequestError> { + if !response.status.is_success() { + return Err(GraphQLRequestError::RequestFailed { response }); + } + #[derive(Debug, Deserialize)] + struct Response { + data: Option, + errors: Option>, + } + let body: Response = match serde_json::from_value(serde_json::Value::Object(response.body)) + { + Ok(body) => body, + Err(error) => { + return Err(GraphQLRequestError::BodyError { + error: Box::new(error), + }) + } + }; + if let Some(errors) = body.errors { + return Err(GraphQLRequestError::RequestErrors { + errors, + data: body.data, + }); + } + let Some(mut body) = body.data else { + return Err(GraphQLRequestError::BodyError { + error: Box::from("body response doesn't contain data field"), + }); + }; + (0..nodes_len) + .map(|idx| { + body.remove(&format!("node{idx}")) + .ok_or_else(|| GraphQLRequestError::BodyError { + error: Box::from(format!( + "expecting response under node key 'node{idx}' but none found" + )), + }) + }) + .collect::, _>>() + } + + #[derive(Debug)] + pub enum GraphQLRequestError { + /// GraphQL errors recieived + RequestErrors { + errors: Vec, + data: Option, + }, + /// Http error codes recieived + RequestFailed { + response: GraphQLResponse, + }, + /// Unable to deserialize body + BodyError { + error: BoxErr, + }, + /// Unable to make http request + NetworkError { + error: BoxErr, + }, + InvalidQuery { + error: BoxErr, + }, + } + + impl std::fmt::Display for GraphQLRequestError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + GraphQLRequestError::RequestErrors { errors, .. } => { + write!(f, "graphql errors in response: ")?; + for err in errors { + write!(f, "{}, ", err.message)?; + } + } + GraphQLRequestError::RequestFailed { response } => { + write!(f, "request failed with status {}", response.status)?; + } + GraphQLRequestError::BodyError { error } => { + write!(f, "error reading request body: {error}")?; + } + GraphQLRequestError::NetworkError { error } => { + write!(f, "error making http request: {error}")?; + } + GraphQLRequestError::InvalidQuery { error } => { + write!(f, "error building request: {error}")? + } + } + Ok(()) + } + } + impl std::error::Error for GraphQLRequestError {} + + #[derive(Debug, Deserialize)] + pub struct ErrorLocation { + pub line: u32, + pub column: u32, + } + #[derive(Debug, Deserialize)] + pub struct GraphqlError { + pub message: String, + pub locations: Option>, + pub path: Option>, + } + + #[derive(Debug)] + pub enum PathSegment { + Field(String), + Index(u64), + } + + impl<'de> serde::de::Deserialize<'de> for PathSegment { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + use serde_json::Value; + let val = Value::deserialize(deserializer)?; + match val { + Value::Number(n) => Ok(PathSegment::Index(n.as_u64().unwrap())), + Value::String(s) => Ok(PathSegment::Field(s)), + _ => panic!("invalid path segment type"), + } + } + } + + #[derive(Clone)] + pub struct GraphQlTransportReqwestSync { + addr: Url, + ty_to_gql_ty_map: TyToGqlTyMap, + client: reqwest::blocking::Client, + } + + #[derive(Clone)] + pub struct GraphQlTransportReqwest { + addr: Url, + ty_to_gql_ty_map: TyToGqlTyMap, + client: reqwest::Client, + } + + impl GraphQlTransportReqwestSync { + pub fn new(addr: Url, ty_to_gql_ty_map: TyToGqlTyMap) -> Self { + Self { + addr, + ty_to_gql_ty_map, + client: reqwest::blocking::Client::new(), + } + } + + fn fetch( + &self, + nodes: Vec, + opts: &GraphQlTransportOptions, + ty: &'static str, + ) -> Result, GraphQLRequestError> { + let nodes_len = nodes.len(); + let (doc, variables, placeholders) = + build_gql_doc(&self.ty_to_gql_ty_map, nodes, ty, None)?; + if !placeholders.is_empty() { + panic!("placeholders found in non-prepared query") + } + let req = build_gql_req(self.addr.clone(), &doc, &variables, opts); + let req = self + .client + .request(req.method, req.addr) + .headers(req.headers) + .json(&req.body); + let req = if let Some(timeout) = opts.timeout { + req.timeout(timeout) + } else { + req + }; + match req.send() { + Ok(res) => { + let status = res.status(); + let headers = res.headers().clone(); + match res.json::() { + Ok(body) => handle_response( + GraphQLResponse { + status, + headers, + body, + }, + nodes_len, + ), + Err(error) => Err(GraphQLRequestError::BodyError { + error: Box::new(error), + }), + } + } + Err(error) => Err(GraphQLRequestError::NetworkError { + error: Box::new(error), + }), + } + } + + pub fn query( + &self, + nodes: Doc, + ) -> Result { + self.query_with_opts(nodes, &Default::default()) + } + + pub fn query_with_opts( + &self, + nodes: Doc, + opts: &GraphQlTransportOptions, + ) -> Result { + let resp = self.fetch(nodes.to_select_doc(), opts, "query")?; + let resp = Doc::parse_response(resp).map_err(|err| GraphQLRequestError::BodyError { + error: Box::from(format!( + "error deserializing response into output type: {err}" + )), + })?; + Ok(resp) + } + + pub fn mutation( + &self, + nodes: Doc, + ) -> Result { + self.mutation_with_opts(nodes, &Default::default()) + } + + pub fn mutation_with_opts( + &self, + nodes: Doc, + opts: &GraphQlTransportOptions, + ) -> Result { + let resp = self.fetch(nodes.to_select_doc(), opts, "mutation")?; + let resp = Doc::parse_response(resp).map_err(|err| GraphQLRequestError::BodyError { + error: Box::from(format!( + "error deserializing response into output type: {err}" + )), + })?; + Ok(resp) + } + pub fn prepare_query( + &self, + fun: impl FnOnce(&mut PreparedArgs) -> Result, + ) -> Result, PrepareRequestError> { + self.prepare_query_with_opts(fun, Default::default()) + } + + pub fn prepare_query_with_opts( + &self, + fun: impl FnOnce(&mut PreparedArgs) -> Result, + opts: GraphQlTransportOptions, + ) -> Result, PrepareRequestError> { + PreparedRequestReqwestSync::new( + fun, + self.addr.clone(), + opts, + "query", + &self.ty_to_gql_ty_map, + ) + } + + pub fn prepare_mutation( + &self, + fun: impl FnOnce(&mut PreparedArgs) -> Result, + ) -> Result, PrepareRequestError> { + self.prepare_mutation_with_opts(fun, Default::default()) + } + + pub fn prepare_mutation_with_opts( + &self, + fun: impl FnOnce(&mut PreparedArgs) -> Result, + opts: GraphQlTransportOptions, + ) -> Result, PrepareRequestError> { + PreparedRequestReqwestSync::new( + fun, + self.addr.clone(), + opts, + "mutation", + &self.ty_to_gql_ty_map, + ) + } + } + + impl GraphQlTransportReqwest { + pub fn new(addr: Url, ty_to_gql_ty_map: TyToGqlTyMap) -> Self { + Self { + addr, + ty_to_gql_ty_map, + client: reqwest::Client::new(), + } + } + + async fn fetch( + &self, + nodes: Vec, + opts: &GraphQlTransportOptions, + ty: &'static str, + ) -> Result, GraphQLRequestError> { + let nodes_len = nodes.len(); + let (doc, variables, placeholders) = + build_gql_doc(&self.ty_to_gql_ty_map, nodes, ty, None)?; + if !placeholders.is_empty() { + panic!("placeholders found in non-prepared query") + } + let req = build_gql_req(self.addr.clone(), &doc, &variables, opts); + let req = self + .client + .request(req.method, req.addr) + .headers(req.headers) + .json(&req.body); + let req = if let Some(timeout) = opts.timeout { + req.timeout(timeout) + } else { + req + }; + match req.send().await { + Ok(res) => { + let status = res.status(); + let headers = res.headers().clone(); + match res.json::().await { + Ok(body) => handle_response( + GraphQLResponse { + status, + headers, + body, + }, + nodes_len, + ), + Err(error) => Err(GraphQLRequestError::BodyError { + error: Box::new(error), + }), + } + } + Err(error) => Err(GraphQLRequestError::NetworkError { + error: Box::new(error), + }), + } + } + + pub async fn query( + &self, + nodes: Doc, + ) -> Result { + self.query_with_opts(nodes, &Default::default()).await + } + + pub async fn query_with_opts( + &self, + nodes: Doc, + opts: &GraphQlTransportOptions, + ) -> Result { + let resp = self.fetch(nodes.to_select_doc(), opts, "query").await?; + let resp = Doc::parse_response(resp).map_err(|err| GraphQLRequestError::BodyError { + error: Box::from(format!( + "error deserializing response into output type: {err}" + )), + })?; + Ok(resp) + } + + pub async fn mutation( + &self, + nodes: Doc, + ) -> Result { + self.mutation_with_opts(nodes, &Default::default()).await + } + + pub async fn mutation_with_opts( + &self, + nodes: Doc, + opts: &GraphQlTransportOptions, + ) -> Result { + let resp = self.fetch(nodes.to_select_doc(), opts, "mutation").await?; + let resp = Doc::parse_response(resp).map_err(|err| GraphQLRequestError::BodyError { + error: Box::from(format!( + "error deserializing response into output type: {err}" + )), + })?; + Ok(resp) + } + pub fn prepare_query( + &self, + fun: impl FnOnce(&mut PreparedArgs) -> Result, + ) -> Result, PrepareRequestError> { + self.prepare_query_with_opts(fun, Default::default()) + } + + pub fn prepare_query_with_opts( + &self, + fun: impl FnOnce(&mut PreparedArgs) -> Result, + opts: GraphQlTransportOptions, + ) -> Result, PrepareRequestError> { + PreparedRequestReqwest::new( + fun, + self.addr.clone(), + opts, + "query", + &self.ty_to_gql_ty_map, + ) + } + + pub fn prepare_mutation( + &self, + fun: impl FnOnce(&mut PreparedArgs) -> Result, + ) -> Result, PrepareRequestError> { + self.prepare_mutation_with_opts(fun, Default::default()) + } + + pub fn prepare_mutation_with_opts( + &self, + fun: impl FnOnce(&mut PreparedArgs) -> Result, + opts: GraphQlTransportOptions, + ) -> Result, PrepareRequestError> { + PreparedRequestReqwest::new( + fun, + self.addr.clone(), + opts, + "mutation", + &self.ty_to_gql_ty_map, + ) + } + } + + fn resolve_prepared_variables( + placeholders: &FoundPlaceholders, + mut inline_variables: JsonObject, + mut args: HashMap, + ) -> Result { + for (ph, key_map) in placeholders { + let Some(value) = args.remove(&ph.key) else { + return Err(PrepareRequestError::PlaceholderError(Box::from(format!( + "no value found for placeholder expected under key '{}'", + ph.key + )))); + }; + let value = (ph.fun)(value).map_err(|err| { + PrepareRequestError::PlaceholderError(Box::from(format!( + "error applying placeholder closure for value under key '{}': {err}", + ph.key + ))) + })?; + let serde_json::Value::Object(mut value) = value else { + unreachable!("placeholder closures must return structs"); + }; + for (key, var_key) in key_map { + inline_variables.insert( + var_key.clone().into(), + value.remove(&key[..]).unwrap_or(serde_json::Value::Null), + ); + } + } + Ok(inline_variables) + } + + pub struct PreparedRequestReqwest { + addr: Url, + client: reqwest::Client, + nodes_len: usize, + doc: String, + variables: JsonObject, + opts: GraphQlTransportOptions, + placeholders: Arc, + _phantom: PhantomData, + } + + pub struct PreparedRequestReqwestSync { + addr: Url, + client: reqwest::blocking::Client, + nodes_len: usize, + doc: String, + variables: JsonObject, + opts: GraphQlTransportOptions, + placeholders: Arc, + _phantom: PhantomData, + } + + impl PreparedRequestReqwestSync { + fn new( + fun: impl FnOnce(&mut PreparedArgs) -> Result, + addr: Url, + opts: GraphQlTransportOptions, + ty: &'static str, + ty_to_gql_ty_map: &TyToGqlTyMap, + ) -> Result { + let nodes = fun(&mut PreparedArgs).map_err(PrepareRequestError::FunctionError)?; + let nodes = nodes.to_select_doc(); + let nodes_len = nodes.len(); + let (doc, variables, placeholders) = build_gql_doc(ty_to_gql_ty_map, nodes, ty, None) + .map_err(PrepareRequestError::BuildError)?; + Ok(Self { + doc, + variables, + nodes_len, + addr, + client: reqwest::blocking::Client::new(), + opts, + placeholders: Arc::new(placeholders), + _phantom: PhantomData, + }) + } + + pub fn perform( + &self, + args: impl Into>, + ) -> Result + where + K: Into, + V: serde::Serialize, + { + let args: HashMap = args.into(); + let args = args + .into_iter() + .map(|(key, val)| (key.into(), to_json_value(val))) + .collect(); + let variables = + resolve_prepared_variables(&self.placeholders, self.variables.clone(), args)?; + let req = build_gql_req(self.addr.clone(), &self.doc, &variables, &self.opts); + let req = self + .client + .request(req.method, req.addr) + .headers(req.headers) + .json(&req.body); + let req = if let Some(timeout) = self.opts.timeout { + req.timeout(timeout) + } else { + req + }; + let res = match req.send() { + Ok(res) => { + let status = res.status(); + let headers = res.headers().clone(); + match res.json::() { + Ok(body) => handle_response( + GraphQLResponse { + status, + headers, + body, + }, + self.nodes_len, + ) + .map_err(PrepareRequestError::RequestError)?, + Err(error) => { + return Err(PrepareRequestError::RequestError( + GraphQLRequestError::BodyError { + error: Box::new(error), + }, + )) + } + } + } + Err(error) => { + return Err(PrepareRequestError::RequestError( + GraphQLRequestError::NetworkError { + error: Box::new(error), + }, + )) + } + }; + Doc::parse_response(res).map_err(|err| { + PrepareRequestError::RequestError(GraphQLRequestError::BodyError { + error: Box::from(format!( + "error deserializing response into output type: {err}" + )), + }) + }) + } + } + + impl PreparedRequestReqwest { + fn new( + fun: impl FnOnce(&mut PreparedArgs) -> Result, + addr: Url, + opts: GraphQlTransportOptions, + ty: &'static str, + ty_to_gql_ty_map: &TyToGqlTyMap, + ) -> Result { + let nodes = fun(&mut PreparedArgs).map_err(PrepareRequestError::FunctionError)?; + let nodes = nodes.to_select_doc(); + let nodes_len = nodes.len(); + let (doc, variables, placeholders) = build_gql_doc(ty_to_gql_ty_map, nodes, ty, None) + .map_err(PrepareRequestError::BuildError)?; + let placeholders = std::sync::Arc::new(placeholders); + Ok(Self { + doc, + variables, + nodes_len, + addr, + client: reqwest::Client::new(), + opts, + placeholders, + _phantom: PhantomData, + }) + } + + pub async fn perform( + &self, + args: impl Into>, + ) -> Result + where + K: Into, + V: serde::Serialize, + { + let args: HashMap = args.into(); + let args = args + .into_iter() + .map(|(key, val)| (key.into(), to_json_value(val))) + .collect(); + let variables = + resolve_prepared_variables(&self.placeholders, self.variables.clone(), args)?; + let req = build_gql_req(self.addr.clone(), &self.doc, &variables, &self.opts); + let req = self + .client + .request(req.method, req.addr) + .headers(req.headers) + .json(&req.body); + let req = if let Some(timeout) = self.opts.timeout { + req.timeout(timeout) + } else { + req + }; + let res = match req.send().await { + Ok(res) => { + let status = res.status(); + let headers = res.headers().clone(); + match res.json::().await { + Ok(body) => handle_response( + GraphQLResponse { + status, + headers, + body, + }, + self.nodes_len, + ) + .map_err(PrepareRequestError::RequestError)?, + Err(error) => { + return Err(PrepareRequestError::RequestError( + GraphQLRequestError::BodyError { + error: Box::new(error), + }, + )) + } + } + } + Err(error) => { + return Err(PrepareRequestError::RequestError( + GraphQLRequestError::NetworkError { + error: Box::new(error), + }, + )) + } + }; + Doc::parse_response(res).map_err(|err| { + PrepareRequestError::RequestError(GraphQLRequestError::BodyError { + error: Box::from(format!( + "error deserializing response into output type: {err}" + )), + }) + }) + } + } + + // we need a manual clone impl since the derive will + // choke if Doc isn't clone + impl Clone for PreparedRequestReqwestSync { + fn clone(&self) -> Self { + Self { + addr: self.addr.clone(), + client: self.client.clone(), + nodes_len: self.nodes_len, + doc: self.doc.clone(), + variables: self.variables.clone(), + opts: self.opts.clone(), + placeholders: self.placeholders.clone(), + _phantom: PhantomData, + } + } + } + impl Clone for PreparedRequestReqwest { + fn clone(&self) -> Self { + Self { + addr: self.addr.clone(), + client: self.client.clone(), + nodes_len: self.nodes_len, + doc: self.doc.clone(), + variables: self.variables.clone(), + opts: self.opts.clone(), + placeholders: self.placeholders.clone(), + _phantom: PhantomData, + } + } + } + + #[derive(Debug)] + pub enum PrepareRequestError { + FunctionError(BoxErr), + BuildError(GraphQLRequestError), + PlaceholderError(BoxErr), + RequestError(GraphQLRequestError), + } + + impl std::error::Error for PrepareRequestError {} + impl std::fmt::Display for PrepareRequestError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + PrepareRequestError::FunctionError(err) => { + write!(f, "error calling doc builder closure: {err}") + } + PrepareRequestError::BuildError(err) => write!(f, "error building request: {err}"), + PrepareRequestError::PlaceholderError(err) => { + write!(f, "error resolving placeholder values: {err}") + } + PrepareRequestError::RequestError(err) => { + write!(f, "error making graphql request: {err}") + } + } + } + } +} + +// +// --- --- QueryGraph types --- --- // +// + +#[derive(Clone)] +pub struct QueryGraph { + ty_to_gql_ty_map: TyToGqlTyMap, + addr: Url, +} + +impl QueryGraph { + pub fn graphql(&self) -> GraphQlTransportReqwest { + GraphQlTransportReqwest::new(self.addr.clone(), self.ty_to_gql_ty_map.clone()) + } + pub fn graphql_sync(&self) -> GraphQlTransportReqwestSync { + GraphQlTransportReqwestSync::new(self.addr.clone(), self.ty_to_gql_ty_map.clone()) + } +} + +// +// --- --- Typegraph types --- --- // +// + +#[allow(non_snake_case)] +mod node_metas { + use super::*; + pub fn scalar() -> NodeMeta { + NodeMeta { + arg_types: None, + sub_nodes: None, + variants: None, + } + } + pub fn Post() -> NodeMeta { + NodeMeta { + arg_types: None, + variants: None, + sub_nodes: Some( + [ + ("id".into(), scalar as NodeMetaFn), + ("slug".into(), scalar as NodeMetaFn), + ("title".into(), scalar as NodeMetaFn), + ] + .into(), + ), + } + } + pub fn Func27() -> NodeMeta { + NodeMeta { ..Post() } + } + pub fn User() -> NodeMeta { + NodeMeta { + arg_types: None, + variants: None, + sub_nodes: Some( + [ + ("id".into(), scalar as NodeMetaFn), + ("email".into(), scalar as NodeMetaFn), + ("posts".into(), Post as NodeMetaFn), + ] + .into(), + ), + } + } + pub fn Func23() -> NodeMeta { + NodeMeta { ..User() } + } + pub fn Func24() -> NodeMeta { + NodeMeta { ..Post() } + } + pub fn Func28() -> NodeMeta { + NodeMeta { + arg_types: Some([("id".into(), "String13".into())].into()), + ..Post() + } + } + pub fn Func26() -> NodeMeta { + NodeMeta { + arg_types: Some( + [ + ("id".into(), "String4".into()), + ("slug".into(), "String1".into()), + ("title".into(), "String1".into()), + ] + .into(), + ), + ..scalar() + } + } + pub fn Func25() -> NodeMeta { + NodeMeta { ..scalar() } + } +} +use types::*; +pub mod types { + pub type StringUuid4 = String; + #[derive(Debug, serde::Serialize, serde::Deserialize)] + pub struct PostPartial { + pub id: Option, + pub slug: Option, + pub title: Option, + } + #[derive(Debug, serde::Serialize, serde::Deserialize)] + pub struct Object21Partial { + pub id: Option, + } + pub type StringEmail5 = String; + pub type Post7 = Vec; + #[derive(Debug, serde::Serialize, serde::Deserialize)] + pub struct UserPartial { + pub id: Option, + pub email: Option, + pub posts: Option, + } +} +#[derive(Default, Debug)] +pub struct PostSelections { + pub id: ScalarSelect, + pub slug: ScalarSelect, + pub title: ScalarSelect, +} +impl_selection_traits!(PostSelections, id, slug, title); +#[derive(Default, Debug)] +pub struct UserSelections { + pub id: ScalarSelect, + pub email: ScalarSelect, + pub posts: CompositeSelect, ATy>, +} +impl_selection_traits!(UserSelections, id, email, posts); + +impl QueryGraph { + pub fn new(addr: Url) -> Self { + Self { + addr, + ty_to_gql_ty_map: std::sync::Arc::new( + [ + ("String4".into(), "ID!".into()), + ("String1".into(), "String!".into()), + ("String13".into(), "String!".into()), + ] + .into(), + ), + } + } + + pub fn get_user( + &self, + ) -> UnselectedNode, QueryMarker, UserPartial> { + UnselectedNode { + root_name: "getUser".into(), + root_meta: node_metas::Func23, + args: NodeArgsErased::None, + _marker: PhantomData, + } + } + pub fn get_posts( + &self, + ) -> UnselectedNode, QueryMarker, PostPartial> { + UnselectedNode { + root_name: "getPosts".into(), + root_meta: node_metas::Func24, + args: NodeArgsErased::None, + _marker: PhantomData, + } + } + pub fn scalar_no_args(&self) -> QueryNode { + let nodes = selection_to_node_set( + SelectionErasedMap([("scalarNoArgs".into(), SelectionErased::Scalar)].into()), + &[("scalarNoArgs".into(), node_metas::Func25 as NodeMetaFn)].into(), + "$q".into(), + ) + .unwrap(); + QueryNode(nodes.into_iter().next().unwrap(), PhantomData) + } + pub fn scalar_args(&self, args: impl Into>) -> MutationNode { + let nodes = selection_to_node_set( + SelectionErasedMap( + [( + "scalarArgs".into(), + SelectionErased::ScalarArgs(args.into().into()), + )] + .into(), + ), + &[("scalarArgs".into(), node_metas::Func26 as NodeMetaFn)].into(), + "$q".into(), + ) + .unwrap(); + MutationNode(nodes.into_iter().next().unwrap(), PhantomData) + } + pub fn composite_no_args( + &self, + ) -> UnselectedNode, MutationMarker, PostPartial> { + UnselectedNode { + root_name: "compositeNoArgs".into(), + root_meta: node_metas::Func27, + args: NodeArgsErased::None, + _marker: PhantomData, + } + } + pub fn composite_args( + &self, + args: impl Into>, + ) -> UnselectedNode, MutationMarker, PostPartial> { + UnselectedNode { + root_name: "compositeArgs".into(), + root_meta: node_metas::Func28, + args: args.into().into(), + _marker: PhantomData, + } + } +} diff --git a/typegate/tests/metagen/typegraphs/sample/rs/main.rs b/typegate/tests/metagen/typegraphs/sample/rs/main.rs new file mode 100644 index 0000000000..cfeebf4c22 --- /dev/null +++ b/typegate/tests/metagen/typegraphs/sample/rs/main.rs @@ -0,0 +1,173 @@ +// Copyright Metatype OÜ, licensed under the Elastic License 2.0. +// SPDX-License-Identifier: Elastic-2.0 + +pub mod client; +use client::*; + +fn main() -> Result<(), BoxErr> { + let port = std::env::var("TG_PORT")?; + let api1 = QueryGraph::new(format!("http://localhost:{port}/sample").parse()?); + + let (res2, res3) = { + // blocking reqwest uses tokio under the hood + let gql_sync = api1.graphql_sync(); + let res3 = gql_sync.query(( + api1.get_user().select_aliased(UserSelections { + posts: alias([ + ( + "post1", + select(PostSelections { + id: get(), + slug: get(), + title: get(), + }), + ), + ("post2", select(PostSelections { id: get(), ..all() })), + ]), + ..all() + })?, + api1.get_posts().select(all())?, + api1.scalar_no_args(), + ))?; + let prepared_m = gql_sync.prepare_mutation(|args| { + Ok(( + api1.scalar_args(args.get("post", |val: types::PostPartial| val)), + api1.composite_no_args().select(all())?, + api1.composite_args( + args.get("id", |id: String| types::Object21Partial { id: Some(id) }), + ) + .select(all())?, + )) + })?; + + let prepared_clone = prepared_m.clone(); + let res2 = prepared_clone.perform([ + ( + "post", + serde_json::json!(types::PostPartial { + id: Some("94be5420-8c4a-4e67-b4f4-e1b2b54832a2".into()), + slug: Some("".into()), + title: Some("".into()), + }), + ), + ( + "id", + serde_json::json!("94be5420-8c4a-4e67-b4f4-e1b2b54832a2"), + ), + ])?; + (res2, res3) + }; + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build()? + .block_on(async move { + let gql = api1.graphql(); + let prepared_q = gql.prepare_query(|_args| { + Ok(( + api1.get_user().select_aliased(UserSelections { + posts: alias([ + ( + "post1", + select(PostSelections { + id: get(), + slug: get(), + title: get(), + }), + ), + ("post2", select(PostSelections { id: get(), ..all() })), + ]), + ..all() + })?, + api1.get_posts().select(all())?, + api1.scalar_no_args(), + )) + })?; + + let res1 = prepared_q.perform::([]).await?; + let res1a = prepared_q.perform::([]).await?; + + let res4 = gql + .mutation(( + api1.scalar_args(types::PostPartial { + id: Some("94be5420-8c4a-4e67-b4f4-e1b2b54832a2".into()), + slug: Some("".into()), + title: Some("".into()), + }), + api1.composite_no_args().select(all())?, + api1.composite_args(types::Object21Partial { + id: Some("94be5420-8c4a-4e67-b4f4-e1b2b54832a2".into()), + }) + .select(all())?, + )) + .await?; + + /* let res5 = gql + .query(( + api1.scalar_union(types::Object28Partial { + id: Some("94be5420-8c4a-4e67-b4f4-e1b2b54832a2".into()), + }), + // allows ignoring some members + api1.composite_union(types::Object28Partial { + id: Some("94be5420-8c4a-4e67-b4f4-e1b2b54832a2".into()), + }) + .select(Union9Selections { + post: select(all()), + ..default() + })?, + // returns empty if returned type wasn't selected + // in union member + api1.composite_union(types::Object28Partial { + id: Some("94be5420-8c4a-4e67-b4f4-e1b2b54832a2".into()), + }) + .select(Union9Selections { + user: select(all()), + ..default() + })?, + api1.mixed_union(types::Object28Partial { + id: Some("94be5420-8c4a-4e67-b4f4-e1b2b54832a2".into()), + }) + .select(Union15Selections { + post: select(all()), + user: select(all()), + })?, + )) + .await?; */ + println!( + "{}", + serde_json::to_string_pretty(&serde_json::json!([ + { + "user": res1.0, + "posts": res1.1, + "scalarNoArgs": res1.2, + }, + { + "user": res1a.0, + "posts": res1a.1, + "scalarNoArgs": res1a.2, + }, + { + "scalarArgs": res2.0, + "compositeNoArgs": res2.1, + "compositeArgs": res2.2, + }, + { + "user": res3.0, + "posts": res3.1, + "scalarNoArgs": res3.2, + }, + { + "scalarArgs": res4.0, + "compositeNoArgs": res4.1, + "compositeArgs": res4.2, + }, + /* { + "scalarUnion": res5.0, + "compositeUnion1": res5.1, + "compositeUnion2": res5.2, + "mixedUnion": res5.3 + } */ + ]))? + ); + Ok(()) + }) +} diff --git a/typegate/tests/metagen/typegraphs/sample/ts/client.ts b/typegate/tests/metagen/typegraphs/sample/ts/client.ts new file mode 100644 index 0000000000..cd65928c03 --- /dev/null +++ b/typegate/tests/metagen/typegraphs/sample/ts/client.ts @@ -0,0 +1,796 @@ +// This file was @generated by metagen and is intended +// to be generated again on subsequent metagen runs. + +function _selectionToNodeSet( + selection: Selection, + metas: [string, () => NodeMeta][], + parentPath: string, +): SelectNode[] { + const out = [] as SelectNode[]; + const selectAll = selection._ == "selectAll"; + // set of the user specified nodes to do sanity + // check at the end + const foundNodes = new Set(Object.keys(selection)); + + for ( + const [nodeName, metaFn] of metas + ) { + foundNodes.delete(nodeName); + + const nodeSelection = selection[nodeName]; + if (!nodeSelection && !selectAll) { + // this node was not selected + continue; + } + + const { argumentTypes, subNodes } = metaFn(); + + const nodeInstances = nodeSelection instanceof Alias + ? nodeSelection.aliases() + : { [nodeName]: nodeSelection }; + + for ( + const [instanceName, instanceSelection] of Object.entries(nodeInstances) + ) { + if (!instanceSelection && !selectAll) { + continue; + } + if (instanceSelection instanceof Alias) { + throw new Error( + `nested Alias discovored at ${parentPath}.${instanceName}`, + ); + } + const node: SelectNode = { instanceName, nodeName }; + + if (argumentTypes) { + // make sure the arg is of the expected form + let arg = instanceSelection; + if (Array.isArray(arg)) { + arg = arg[0]; + } + // TODO: consider bringing in Zod (after hoisting impl into common lib) + if (typeof arg != "object" || arg === null) { + throw new Error( + `node at ${parentPath}.${instanceName} is a node ` + + `that requires arguments object but detected argument ` + + `is typeof ${typeof arg}`, + ); + } + + const expectedArguments = new Map(Object.entries(argumentTypes)); + node.args = {}; + for (const [key, value] of Object.entries(arg)) { + const typeName = expectedArguments.get(key); + // TODO: consider logging a warning if `_` is detected incase user passes + // Selection as arg + if (!typeName) { + throw new Error( + `unexpected argument ${key} at ${parentPath}.${instanceName}`, + ); + } + expectedArguments.delete(key); + node.args[key] = { typeName, value }; + } + } + + if (subNodes) { + // sanity check selection object + let subSelections = instanceSelection; + if (argumentTypes) { + if (!Array.isArray(subSelections)) { + throw new Error( + `node at ${parentPath}.${instanceName} ` + + `is a composite that takes an argument ` + + `but selection is typeof ${typeof subSelections}`, + ); + } + subSelections = subSelections[1]; + } else if (Array.isArray(subSelections)) { + throw new Error( + `node at ${parentPath}.${instanceName} ` + + `is a composite that takes no arguments ` + + `but selection is typeof ${typeof subSelections}`, + ); + } + if (typeof subSelections != "object") { + throw new Error( + `node at ${parentPath}.${nodeName} ` + + `is a no argument composite but first element of ` + + `selection is typeof ${typeof nodeSelection}`, + ); + } + + node.subNodes = _selectionToNodeSet( + // assume it's a Selection. If it's an argument + // object, mismatch between the node desc should hopefully + // catch it + subSelections as Selection, + subNodes, + `${parentPath}.${instanceName}`, + ); + } + + out.push(node); + } + } + foundNodes.delete("_"); + if (foundNodes.size > 0) { + throw new Error( + `unexpected nodes found in selection set at ${parentPath}: ${[ + ...foundNodes, + ]}`, + ); + } + return out; +} + +/* Query node types section */ + +type SelectNode<_Out = unknown> = { + nodeName: string; + instanceName: string; + args?: NodeArgs; + subNodes?: SelectNode[]; +}; + +export class QueryNode { + #inner: SelectNode; + constructor( + inner: SelectNode, + ) { + this.#inner = inner; + } + + inner() { + return this.#inner; + } +} + +export class MutationNode { + #inner: SelectNode; + constructor( + inner: SelectNode, + ) { + this.#inner = inner; + } + + inner() { + return this.#inner; + } +} + +type SelectNodeOut = T extends (QueryNode | MutationNode) + ? O + : never; +type QueryDocOut = T extends + Record | MutationNode> ? { + [K in keyof T]: SelectNodeOut; + } + : never; + +type NodeMeta = { + subNodes?: [string, () => NodeMeta][]; + argumentTypes?: { [name: string]: string }; +}; + +/* Selection types section */ + +type SelectionFlags = "selectAll"; + +type Selection = { + _?: SelectionFlags; + [key: string]: + | SelectionFlags + | ScalarSelectNoArgs + | ScalarSelectArgs> + | CompositeSelectNoArgs + | CompositeSelectArgs, Selection> + | Selection; +}; + +type ScalarSelectNoArgs = + | boolean + | Alias + | null + | undefined; + +type ScalarSelectArgs> = + | ArgT + | PlaceholderArgs + | Alias> + | false + | null + | undefined; + +type CompositeSelectNoArgs = + | SelectionT + | Alias + | false + | null + | undefined; + +type CompositeSelectArgs, SelectionT> = + | [ArgT | PlaceholderArgs, SelectionT] + | Alias<[ArgT | PlaceholderArgs, SelectionT]> + | false + | undefined + | null; + +/** + * Request multiple instances of a single node under different + * aliases. Look at {@link alias} for a functional way of instantiating + * this class. + */ +export class Alias { + #aliases: Record; + constructor( + aliases: Record, + ) { + this.#aliases = aliases; + } + aliases() { + return this.#aliases; + } +} + +/** + * Request multiple instances of a single node under different + * aliases. + */ +export function alias(aliases: Record): Alias { + return new Alias(aliases); +} + +/* Argument types section */ + +type NodeArgValue = { + typeName: string; + value: unknown; +}; + +type NodeArgs = { + [name: string]: NodeArgValue; +}; + +/** + * This object is passed to closures used for preparing requests + * ahead of time for {@link PreparedRequest}s. It allows one to + * get {@link PlaceholderValue}s that can be used in place of node + * arguments. At request time, the {@link PreparedRequest} then + * takes an object that adheres to `T` that can then be used + * to replace the placeholders. + */ +export class PreparedArgs> { + get(key: OnlyStringKeys): PlaceholderValue { + return new PlaceholderValue(key); + } +} + +/** + * Placeholder values for use by {@link PreparedRequest} + */ +export class PlaceholderValue<_T> { + #key: string; + constructor(key: string) { + this.#key = key; + } + + key() { + return this.#key; + } +} + +export type PlaceholderArgs> = { + [K in keyof T]: PlaceholderValue; +}; + +/* GraphQL section */ + +/** + * Options to be used for requests performed by {@link GraphQLTransport}. + */ +export type GraphQlTransportOptions = Omit & { + /** + * {@link fetch} implementaiton to use. Defaults to the one found in the environment + */ + fetch?: typeof fetch; +}; + +function convertQueryNodeGql( + node: SelectNode, + variables: Map, +) { + let out = node.nodeName == node.instanceName + ? node.nodeName + : `${node.instanceName}: ${node.nodeName}`; + + const args = node.args; + if (args) { + out = `${out} (${ + Object.entries(args) + .map(([key, val]) => { + const name = `in${variables.size}`; + variables.set(name, val); + return `${key}: $${name}`; + }) + .join(", ") + })`; + } + + const subNodes = node.subNodes; + if (subNodes) { + out = `${out} { ${ + subNodes.map((node) => convertQueryNodeGql(node, variables)).join(" ") + } }`; + } + return out; +} + +function buildGql( + typeToGqlTypeMap: Record, + query: Record, + ty: "query" | "mutation", + name: string = "", +) { + const variables = new Map(); + + const rootNodes = Object + .entries(query) + .map(([key, node]) => { + const fixedNode = { ...node, instanceName: key }; + return convertQueryNodeGql(fixedNode, variables); + }) + .join("\n "); + + let argsRow = [...variables.entries()] + .map(([key, val]) => `$${key}: ${typeToGqlTypeMap[val.typeName]}`) + .join(", "); + if (argsRow.length > 0) { + // graphql doesn't like empty parentheses so we only + // add them if there are args + argsRow = `(${argsRow})`; + } + + const doc = `${ty} ${name}${argsRow} { + ${rootNodes} +}`; + return { + doc, + variables: Object.fromEntries( + [...variables.entries()] + .map(([key, val]) => [key, val.value]), + ), + }; +} + +async function fetchGql( + addr: URL, + doc: string, + variables: Record, + options: GraphQlTransportOptions, +) { + // console.log(doc, variables); + const fetchImpl = options.fetch ?? fetch; + const res = await fetchImpl(addr, { + ...options, + method: "POST", + headers: { + accept: "application/json", + "content-type": "application/json", + ...options.headers ?? {}, + }, + body: JSON.stringify({ + query: doc, + variables, + }), + }); + if (!res.ok) { + const body = await res.text().catch((err) => `error reading body: ${err}`); + throw new (Error as ErrorPolyfill)( + `graphql request to ${addr} failed with status ${res.status}: ${body}`, + { + cause: { + response: res, + body, + }, + }, + ); + } + if (res.headers.get("content-type") != "application/json") { + throw new (Error as ErrorPolyfill)( + "unexpected content type in response", + { + cause: { + response: res, + body: await res.text().catch((err) => `error reading body: ${err}`), + }, + }, + ); + } + return await res.json() as { data: unknown; errors?: object[] }; +} + +/** + * Access the typegraph over it's exposed GraphQL API. + */ +export class GraphQLTransport { + constructor( + public address: URL, + public options: GraphQlTransportOptions, + private typeToGqlTypeMap: Record, + ) { + } + + async #request( + doc: string, + variables: Record, + options?: GraphQlTransportOptions, + ) { + const res = await fetchGql(this.address, doc, variables, { + ...this.options, + ...options, + }); + if ("errors" in res) { + throw new (Error as ErrorPolyfill)("graphql errors on response", { + cause: res.errors, + }); + } + return res.data; + } + + /** + * Make a query request to the typegraph. + */ + async query>>( + query: Doc, + { options, name = "" }: { + options?: GraphQlTransportOptions; + name?: string; + } = {}, + ): Promise> { + const { variables, doc } = buildGql( + this.typeToGqlTypeMap, + Object.fromEntries( + Object.entries(query).map(( + [key, val], + ) => [key, (val as QueryNode).inner()]), + ), + "query", + name, + ); + return await this.#request(doc, variables, options) as QueryDocOut; + } + + /** + * Make a mutation request to the typegraph. + */ + async mutation>>( + query: Doc, + { options, name = "" }: { + options?: GraphQlTransportOptions; + name?: string; + } = {}, + ): Promise> { + const { variables, doc } = buildGql( + this.typeToGqlTypeMap, + Object.fromEntries( + Object.entries(query).map(( + [key, val], + ) => [key, (val as MutationNode).inner()]), + ), + "mutation", + name, + ); + return await this.#request(doc, variables, options) as QueryDocOut; + } + + /** + * Prepare an ahead of time query {@link PreparedRequest}. + */ + prepareQuery< + T extends JsonObject, + Doc extends Record>, + >( + fun: (args: PreparedArgs) => Doc, + { name = "" }: { name?: string } = {}, + ): PreparedRequest { + return new PreparedRequest( + this.address, + this.options, + this.typeToGqlTypeMap, + fun, + "query", + name, + ); + } + + /** + * Prepare an ahead of time mutation {@link PreparedRequest}. + */ + prepareMutation< + T extends JsonObject, + Q extends Record>, + >( + fun: (args: PreparedArgs) => Q, + { name = "" }: { name?: string } = {}, + ): PreparedRequest { + return new PreparedRequest( + this.address, + this.options, + this.typeToGqlTypeMap, + fun, + "mutation", + name, + ); + } +} + +/** + * Prepares the GraphQL string ahead of time and allows re-use + * avoid the compute and garbage overhead of re-building it for + * repeat queries. + */ +export class PreparedRequest< + T extends JsonObject, + Doc extends Record | MutationNode>, +> { + public doc: string; + #mappings: Record; + + constructor( + private address: URL, + private options: GraphQlTransportOptions, + typeToGqlTypeMap: Record, + fun: (args: PreparedArgs) => Doc, + ty: "query" | "mutation", + name: string = "", + ) { + const args = new PreparedArgs(); + const dryRunNode = fun(args); + const { doc, variables } = buildGql( + typeToGqlTypeMap, + Object.fromEntries( + Object.entries(dryRunNode).map(( + [key, val], + ) => [key, (val as MutationNode).inner()]), + ), + ty, + name, + ); + this.doc = doc; + this.#mappings = variables; + } + + resolveVariables( + args: T, + mappings: Record, + ) { + const resolvedVariables = {} as Record; + for (const [key, val] of Object.entries(mappings)) { + if (val instanceof PlaceholderValue) { + resolvedVariables[key] = args[val.key()]; + } else if (typeof val == "object" && val != null) { + this.resolveVariables(args, val as JsonObject); + } else { + resolvedVariables[key] = val; + } + } + return resolvedVariables; + } + + /** + * Execute the prepared request. + */ + async perform(args: T, opts?: GraphQlTransportOptions): Promise< + { + [K in keyof Doc]: SelectNodeOut; + } + > { + const resolvedVariables = this.resolveVariables(args, this.#mappings); + // console.log(this.doc, { + // resolvedVariables, + // mapping: this.#mappings, + // }); + const res = await fetchGql( + this.address, + this.doc, + resolvedVariables, + { + ...this.options, + ...opts, + }, + ); + if ("errors" in res) { + throw new (Error as ErrorPolyfill)("graphql errors on response", { + cause: res.errors, + }); + } + return res.data as QueryDocOut; + } +} + +/* Util types section */ + +type OnlyStringKeys> = { + [K in keyof T]: K extends string ? K : never; +}[keyof T]; + +type JsonLiteral = string | number | boolean | null; +type JsonObject = { [key: string]: Json }; +type JsonArray = Json[]; +type Json = JsonLiteral | JsonObject | JsonArray; + +type ErrorPolyfill = new (msg: string, payload: unknown) => Error; + +/* QueryGraph section */ + +class _QueryGraphBase { + constructor(private typeNameMapGql: Record) {} + + /** + * Get the {@link GraphQLTransport} for the typegraph. + */ + graphql(addr: URL | string, options?: GraphQlTransportOptions) { + return new GraphQLTransport( + new URL(addr), + options ?? {}, + this.typeNameMapGql, + ); + } +} + +// -------------------------------------------------- // + +const nodeMetas = { + scalar() { + return {}; + }, + + Func26(): NodeMeta { + return { + ...nodeMetas.scalar(), + argumentTypes: { + id: "String4", + slug: "String1", + title: "String1", + }, + }; + }, + Post(): NodeMeta { + return { + subNodes: [ + ["id", nodeMetas.scalar], + ["slug", nodeMetas.scalar], + ["title", nodeMetas.scalar], + ], + }; + }, + Func28(): NodeMeta { + return { + ...nodeMetas.Post(), + argumentTypes: { + id: "String13", + }, + }; + }, + User(): NodeMeta { + return { + subNodes: [ + ["id", nodeMetas.scalar], + ["email", nodeMetas.scalar], + ["posts", nodeMetas.Post], + ], + }; + }, + Func23(): NodeMeta { + return { + ...nodeMetas.User(), + }; + }, + Func24(): NodeMeta { + return { + ...nodeMetas.Post(), + }; + }, + Func25(): NodeMeta { + return { + ...nodeMetas.scalar(), + }; + }, + Func27(): NodeMeta { + return { + ...nodeMetas.Post(), + }; + }, +}; +export type StringUuid4 = string; +export type Post = { + id: StringUuid4; + slug: string; + title: string; +}; +export type Object21 = { + id: string; +}; +export type StringEmail5 = string; +export type Post7 = Array; +export type User = { + id: StringUuid4; + email: StringEmail5; + posts: Post7; +}; + +export type PostSelections = { + _?: SelectionFlags; + id?: ScalarSelectNoArgs; + slug?: ScalarSelectNoArgs; + title?: ScalarSelectNoArgs; +}; +export type UserSelections = { + _?: SelectionFlags; + id?: ScalarSelectNoArgs; + email?: ScalarSelectNoArgs; + posts?: CompositeSelectNoArgs; +}; + +export class QueryGraph extends _QueryGraphBase { + constructor() { + super({ + "String4": "ID!", + "String1": "String!", + "String13": "String!", + }); + } + + getUser(select: UserSelections) { + const inner = _selectionToNodeSet( + { "getUser": select }, + [["getUser", nodeMetas.Func23]], + "$q", + )[0]; + return new QueryNode(inner) as QueryNode; + } + getPosts(select: PostSelections) { + const inner = _selectionToNodeSet( + { "getPosts": select }, + [["getPosts", nodeMetas.Func24]], + "$q", + )[0]; + return new QueryNode(inner) as QueryNode; + } + scalarNoArgs() { + const inner = _selectionToNodeSet( + { "scalarNoArgs": true }, + [["scalarNoArgs", nodeMetas.Func25]], + "$q", + )[0]; + return new QueryNode(inner) as QueryNode; + } + scalarArgs(args: Post | PlaceholderArgs) { + const inner = _selectionToNodeSet( + { "scalarArgs": args }, + [["scalarArgs", nodeMetas.Func26]], + "$q", + )[0]; + return new MutationNode(inner) as MutationNode; + } + compositeNoArgs(select: PostSelections) { + const inner = _selectionToNodeSet( + { "compositeNoArgs": select }, + [["compositeNoArgs", nodeMetas.Func27]], + "$q", + )[0]; + return new MutationNode(inner) as MutationNode; + } + compositeArgs( + args: Object21 | PlaceholderArgs, + select: PostSelections, + ) { + const inner = _selectionToNodeSet( + { "compositeArgs": [args, select] }, + [["compositeArgs", nodeMetas.Func28]], + "$q", + )[0]; + return new MutationNode(inner) as MutationNode; + } +} diff --git a/typegate/tests/metagen/typegraphs/sample/ts/deno.json b/typegate/tests/metagen/typegraphs/sample/ts/deno.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/typegate/tests/metagen/typegraphs/sample/ts/deno.json @@ -0,0 +1 @@ +{} diff --git a/typegate/tests/metagen/typegraphs/sample/ts/main.ts b/typegate/tests/metagen/typegraphs/sample/ts/main.ts new file mode 100644 index 0000000000..e8abfc47e1 --- /dev/null +++ b/typegate/tests/metagen/typegraphs/sample/ts/main.ts @@ -0,0 +1,85 @@ +// Copyright Metatype OÜ, licensed under the Elastic License 2.0. +// SPDX-License-Identifier: Elastic-2.0 + +import { alias, PreparedArgs, QueryGraph } from "./client.ts"; + +const api1 = new QueryGraph(); + +const gqlClient = api1.graphql( + `http://localhost:${Deno.env.get("TG_PORT")}/sample`, +); + +const preparedQ = gqlClient.prepareQuery(() => ({ + user: api1.getUser({ + _: "selectAll", + posts: alias({ + post1: { id: true, slug: true, title: true }, + post2: { _: "selectAll", id: false }, + }), + }), + posts: api1.getPosts({ _: "selectAll" }), + + scalarNoArgs: api1.scalarNoArgs(), +})); + +const preparedM = gqlClient.prepareMutation(( + args: PreparedArgs<{ + id: string; + slug: string; + title: string; + }>, +) => ({ + scalarArgs: api1.scalarArgs({ + id: args.get("id"), + slug: args.get("slug"), + title: args.get("title"), + }), + compositeNoArgs: api1.compositeNoArgs({ + _: "selectAll", + }), + compositeArgs: api1.compositeArgs({ + id: args.get("id"), + }, { + _: "selectAll", + }), +})); + +const res1 = await preparedQ.perform({}); +const res1a = await preparedQ.perform({}); + +const res2 = await preparedM.perform({ + id: "94be5420-8c4a-4e67-b4f4-e1b2b54832a2", + slug: "s", + title: "t", +}); + +const res3 = await gqlClient.query({ + user: api1.getUser({ + _: "selectAll", + posts: alias({ + post1: { id: true, slug: true, title: true }, + post2: { _: "selectAll", id: false }, + }), + }), + posts: api1.getPosts({ _: "selectAll" }), + + scalarNoArgs: api1.scalarNoArgs(), +}); + +const res4 = await gqlClient.mutation({ + scalarArgs: api1.scalarArgs({ + id: "94be5420-8c4a-4e67-b4f4-e1b2b54832a2", + slug: "", + title: "", + }), + compositeNoArgs: api1.compositeNoArgs({ + _: "selectAll", + }), + compositeArgs: api1.compositeArgs({ + id: "94be5420-8c4a-4e67-b4f4-e1b2b54832a2", + }, { + _: "selectAll", + }), +}); + +console.log(JSON.stringify([res1, res1a, res2, res3, res4])); diff --git a/typegate/tests/metatype.yml b/typegate/tests/metatype.yml index e26c5a24ac..eed689a250 100644 --- a/typegate/tests/metatype.yml +++ b/typegate/tests/metatype.yml @@ -18,6 +18,8 @@ typegates: secrets: prisma: POSTGRES: "postgresql://postgres:password@localhost:5432/db?schema=e2e" + sample: + POSTGRES: "postgresql://postgres:password@localhost:5432/db?schema=sample" migration-failure-test: POSTGRES: "postgresql://postgres:password@localhost:5432/db?schema=e2e2" diff --git a/typegate/tests/runtimes/wasm_reflected/rust/Cargo.lock b/typegate/tests/runtimes/wasm_reflected/rust/Cargo.lock index 7c79ca459e..dd0034b4a2 100644 --- a/typegate/tests/runtimes/wasm_reflected/rust/Cargo.lock +++ b/typegate/tests/runtimes/wasm_reflected/rust/Cargo.lock @@ -114,7 +114,7 @@ dependencies = [ [[package]] name = "rust" -version = "0.4.6" +version = "0.4.8" dependencies = [ "wit-bindgen", ] diff --git a/typegate/tests/runtimes/wasm_wire/rust/mdk.rs b/typegate/tests/runtimes/wasm_wire/rust/mdk.rs index ebad7fe184..cefc44125e 100644 --- a/typegate/tests/runtimes/wasm_wire/rust/mdk.rs +++ b/typegate/tests/runtimes/wasm_wire/rust/mdk.rs @@ -3,7 +3,7 @@ #![cfg_attr(rustfmt, rustfmt_skip)] // gen-static-start -#![allow(unused)] +#![allow(dead_code)] pub mod wit { wit_bindgen::generate!({ @@ -109,7 +109,7 @@ impl Router { } pub fn init(&self, args: InitArgs) -> Result { - static MT_VERSION: &str = "0.4.6"; + static MT_VERSION: &str = "0.4.8"; if args.metatype_version != MT_VERSION { return Err(InitError::VersionMismatch(MT_VERSION.into())); } @@ -219,7 +219,6 @@ macro_rules! init_mat { // gen-static-end use types::*; pub mod types { - use super::*; #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct AddArgs { pub a: f64, @@ -263,7 +262,6 @@ pub mod types { pub type Entity36 = Vec; pub type Entity45 = Vec; } -use stubs::*; pub mod stubs { use super::*; pub trait Add: Sized + 'static { diff --git a/typegraph/deno/sdk/jsr.json b/typegraph/deno/sdk/jsr.json index e1e36ba7f8..9ab8d1f28c 100644 --- a/typegraph/deno/sdk/jsr.json +++ b/typegraph/deno/sdk/jsr.json @@ -12,9 +12,11 @@ "./deps/_import.ts": "./src/deps/_import.ts", "./deps/mod.ts": "./src/deps/mod.ts", "./effects.ts": "./src/effects.ts", + "./envs/cli.ts": "./src/envs/cli.ts", "./gen/typegraph_core.d.ts": "./src/gen/typegraph_core.d.ts", "./host/host.d.ts": "./src/host/host.d.ts", "./index.ts": "./src/index.ts", + "./io.ts": "./src/io.ts", "./metagen.ts": "./src/metagen.ts", "./params.ts": "./src/params.ts", "./policy.ts": "./src/policy.ts", @@ -24,6 +26,7 @@ "./runtimes/deno.ts": "./src/runtimes/deno.ts", "./runtimes/graphql.ts": "./src/runtimes/graphql.ts", "./runtimes/http.ts": "./src/runtimes/http.ts", + "./runtimes/kv.ts": "./src/runtimes/kv.ts", "./runtimes/mod.ts": "./src/runtimes/mod.ts", "./runtimes/python.ts": "./src/runtimes/python.ts", "./runtimes/random.ts": "./src/runtimes/random.ts", @@ -34,6 +37,8 @@ "./tg_manage.ts": "./src/tg_manage.ts", "./typegraph.ts": "./src/typegraph.ts", "./types.ts": "./src/types.ts", + "./utils/func_utils.ts": "./src/utils/func_utils.ts", + "./utils/injection_utils.ts": "./src/utils/injection_utils.ts", "./utils/type_utils.ts": "./src/utils/type_utils.ts", "./wit.ts": "./src/wit.ts" } diff --git a/typegraph/deno/sdk/src/typegraph.ts b/typegraph/deno/sdk/src/typegraph.ts index 4128907e11..e477885234 100644 --- a/typegraph/deno/sdk/src/typegraph.ts +++ b/typegraph/deno/sdk/src/typegraph.ts @@ -217,7 +217,14 @@ export async function typegraph( ...InjectionSource, }; - builder(g); + try { + builder(g); + } catch (err) { + if (err.payload && !err.cause) { + err.cause = err.payload; + } + throw err; + } const ret = { serialize(config: SerializeParams) { diff --git a/website/package.json b/website/package.json index d5f481d1b9..1876d5a80e 100644 --- a/website/package.json +++ b/website/package.json @@ -108,5 +108,6 @@ } } } - } + }, + "packageManager": "pnpm@9.5.0+sha1.8c155dc114e1689d18937974f6571e0ceee66f1d" } diff --git a/whiz.yaml b/whiz.yaml index 9b011d6022..385c847497 100644 --- a/whiz.yaml +++ b/whiz.yaml @@ -53,7 +53,7 @@ setup: - typegraph/python/pyproject.toml - website/package.json env: - GHJK_VERSION: "b702292" + GHJK_VERSION: "v0.2.1" command: | set -e curl -fsSL https://raw.githubusercontent.com/metatypedev/ghjk/$GHJK_VERSION/install.sh | bash