From e19c44dc8fa7ab3c574c6825b49a898ef4b4d798 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl?= Date: Fri, 8 Dec 2023 13:36:41 +0300 Subject: [PATCH] feat(sdk,gate): node information by path (#498) ### Describe your change * Fixes renamed function in sdk * Adds `argInfoByPath` utility function in `typegate.py` ### Motivation and context Make the task of fetching type information from the graphql function args easier ### Migration notes ### Checklist - [x] The change come with new or modified tests - [x] Hard-to-understand functions have explanatory comments - [ ] End-user documentation is updated to reflect the change --------- Co-authored-by: Natoandro --- typegate/src/runtimes/typegate.ts | 204 +++++++++++ typegate/src/typegraphs/typegate.json | 341 ++++++++++++++++-- typegate/src/typegraphs/typegate.py | 41 ++- typegate/tests/e2e/cli/deploy1_test.ts | 130 ------- typegate/tests/e2e/cli/undeploy_test.ts | 30 +- .../tests/e2e/typegraph/validator_test.ts | 4 +- typegraph/core/src/runtimes/mod.rs | 1 + typegraph/core/src/runtimes/typegate.rs | 2 + typegraph/core/wit/typegraph.wit | 1 + 9 files changed, 566 insertions(+), 188 deletions(-) delete mode 100644 typegate/tests/e2e/cli/deploy1_test.ts diff --git a/typegate/src/runtimes/typegate.ts b/typegate/src/runtimes/typegate.ts index 19b410beb0..f6484a40d1 100644 --- a/typegate/src/runtimes/typegate.ts +++ b/typegate/src/runtimes/typegate.ts @@ -10,9 +10,35 @@ import config from "../config.ts"; import * as semver from "std/semver/mod.ts"; import { Typegate } from "../typegate/mod.ts"; import { TypeGraph } from "../typegraph/mod.ts"; +import { closestWord } from "../utils.ts"; +import { Type, TypeNode } from "../typegraph/type_node.ts"; +import { StringFormat } from "../typegraph/types.ts"; const logger = getLogger(import.meta); +interface ArgInfoResult { + optional: boolean; + as_id: boolean; + title: string; + type: string; + runtime: string; + /** list of json string */ + enum: string[] | null; + /** json string */ + config: string | null; + /** json string */ + default: string | null; + /** json string */ + format: StringFormat | null; + fields: Array | null; +} + +interface ObjectNodeResult { + /** path starting from the parent node */ + subPath: Array; + termNode: ArgInfoResult; +} + export class TypeGateRuntime extends Runtime { static singleton: TypeGateRuntime | null = null; @@ -56,6 +82,9 @@ export class TypeGateRuntime extends Runtime { if (name === "serializedTypegraph") { return this.serializedTypegraph; } + if (name === "argInfoByPath") { + return this.argInfoByPath; + } return async ({ _: { parent }, ...args }) => { const resolver = parent[stage.props.node]; @@ -137,4 +166,179 @@ export class TypeGateRuntime extends Runtime { } return true; }; + + argInfoByPath: Resolver = ({ typegraph, queryType, argPaths, fn }) => { + if (queryType != "query" && queryType != "mutation") { + throw new Error( + `"query" or "mutation" expected, got type "${queryType}"`, + ); + } + + const paths = argPaths as Array>; + const tg = this.typegate.register.get(typegraph); + + const root = tg!.tg.type(0, Type.OBJECT).properties[queryType]; + const exposed = tg!.tg.type(root, Type.OBJECT).properties; + + const funcIdx = exposed[fn]; + if (funcIdx === undefined) { + const available = Object.keys(exposed); + const closest = closestWord(fn, available, true); + const propositions = closest ? [closest] : available; + const prefix = propositions.length > 1 ? " one of " : " "; + throw new Error( + `type named "${fn}" not found, did you mean${prefix}${ + propositions + .map((prop) => `"${prop}"`) + .join(", ") + }?`, + ); + } + + const func = tg!.tg.type(funcIdx, Type.FUNCTION); + const input = tg!.tg.type(func.input, Type.OBJECT); + + return paths.map((path) => walkPath(tg!.tg, input, 0, path)); + }; +} + +function resolveOptional(tg: TypeGraph, node: TypeNode) { + let topLevelDefault; + let isOptional = false; + if (node.type == Type.OPTIONAL) { + while (node.type == Type.OPTIONAL) { + if (topLevelDefault == undefined) { + topLevelDefault = node.default_value; + } + isOptional = true; + node = tg.type(node.item); + } + } + const format = node.type == Type.STRING ? node.format : undefined; + return { node, format, topLevelDefault, isOptional }; +} + +function collectObjectFields( + tg: TypeGraph, + parent: TypeNode, +): Array { + // first generate all possible paths + + const paths = [] as Array>; + + const collectAllPaths = ( + parent: TypeNode, + currentPath: Array = [], + ): void => { + const node = resolveOptional(tg, parent).node; + + if (node.type == Type.OBJECT) { + for (const [keyName, fieldIdx] of Object.entries(node.properties)) { + collectAllPaths(tg.type(fieldIdx), [...currentPath, keyName]); + } + return; + } + + // leaf + // TODO: either/union? + paths.push(currentPath); + }; + + collectAllPaths(parent); + + return paths.map((path) => ({ + subPath: path, + termNode: walkPath(tg, parent, 0, path), + })); +} + +function walkPath( + tg: TypeGraph, + parent: TypeNode, + startCursor: number, + path: Array, +): ArgInfoResult { + let node = parent as TypeNode; + for (let cursor = startCursor; cursor < path.length; cursor += 1) { + const current = path.at(cursor)!; + + // if the type is optional and path has not ended yet, the wrapped type needs to be retrieved + node = resolveOptional(tg, node).node; + + const prettyPath = path.map((chunk, i) => + i == cursor ? `[${chunk}]` : chunk + ).join("."); + + switch (node.type) { + case Type.OBJECT: { + const available = Object.keys(node.properties); + const currNodeIdx = node.properties[current]; + + if (currNodeIdx === undefined) { + throw new Error( + `invalid path ${prettyPath}, none of ${ + available.join(", ") + } match the chunk "${current}"`, + ); + } + + node = tg.type(currNodeIdx); + break; + } + case Type.EITHER: + case Type.UNION: { + const variantsIdx = "anyOf" in node ? node.anyOf : node.oneOf; + const failures = new Array(variantsIdx.length); + // try to expand each variant, return first compatible with the path + const compat = []; + for (let i = 0; i < variantsIdx.length; i += 1) { + const variant = tg.type(variantsIdx[i]); + try { + compat.push(walkPath(tg, variant, cursor, path)); + } catch (err) { + failures[i] = err; + } + } + if (compat.length == 0) { + throw failures.shift(); + } + return compat.shift()!; + } + default: { + // optional, list, float are considered as leaf + if (cursor != path.length) { + throw new Error( + `cannot extend path ${prettyPath} with type "${node.type}"`, + ); + } + break; + } + } + } + + // resulting leaf can be optional + // in that case isOptional is true + const { + node: resNode, + format, + topLevelDefault: defaultValue, + isOptional, + } = resolveOptional( + tg, + node, + ); + node = resNode; + + return { + optional: isOptional, + as_id: node.as_id, + title: node.title, + type: node.type, + enum: node.enum ?? null, + runtime: tg.runtime(node.runtime).name, + config: node.config ? JSON.stringify(node.config) : null, + default: defaultValue ? JSON.stringify(defaultValue) : null, + format: format ?? null, + fields: node.type == "object" ? collectObjectFields(tg, parent) : null, + }; } diff --git a/typegate/src/typegraphs/typegate.json b/typegate/src/typegraphs/typegate.json index 31336db457..ea392e44a4 100644 --- a/typegate/src/typegraphs/typegate.json +++ b/typegate/src/typegraphs/typegate.json @@ -12,18 +12,20 @@ "typegraphs": 1, "typegraph": 7, "addTypegraph": 15, - "removeTypegraphs": 32 + "removeTypegraphs": 32, + "argInfoByPath": 37 }, "required": [ "typegraphs", "typegraph", "addTypegraph", - "removeTypegraphs" + "removeTypegraphs", + "argInfoByPath" ] }, { "type": "function", - "title": "func_36", + "title": "func_65", "runtime": 1, "policies": [ 0 @@ -38,7 +40,7 @@ }, { "type": "object", - "title": "object_6", + "title": "object_33", "runtime": 1, "policies": [], "config": {}, @@ -48,7 +50,7 @@ }, { "type": "list", - "title": "_7_Typegraph[]", + "title": "_34_Typegraph[]", "runtime": 1, "policies": [], "config": {}, @@ -87,7 +89,7 @@ }, { "type": "function", - "title": "func_37", + "title": "func_66", "runtime": 1, "policies": [ 0 @@ -102,7 +104,7 @@ }, { "type": "object", - "title": "object_10", + "title": "object_37", "runtime": 1, "policies": [], "config": {}, @@ -114,7 +116,7 @@ }, { "type": "string", - "title": "string_9", + "title": "string_36", "runtime": 1, "policies": [], "config": {}, @@ -122,7 +124,7 @@ }, { "type": "optional", - "title": "optional_12", + "title": "optional_39", "runtime": 1, "policies": [], "config": {}, @@ -132,7 +134,7 @@ }, { "type": "object", - "title": "object_11", + "title": "object_38", "runtime": 1, "policies": [], "config": {}, @@ -177,7 +179,7 @@ }, { "type": "function", - "title": "func_38", + "title": "func_67", "runtime": 1, "policies": [ 0 @@ -192,7 +194,7 @@ }, { "type": "object", - "title": "object_17", + "title": "object_44", "runtime": 1, "policies": [], "config": {}, @@ -206,7 +208,7 @@ }, { "type": "string", - "title": "string_14", + "title": "string_41", "runtime": 1, "policies": [], "config": {}, @@ -215,7 +217,7 @@ }, { "type": "string", - "title": "string_15", + "title": "string_42", "runtime": 1, "policies": [], "config": {}, @@ -224,7 +226,7 @@ }, { "type": "string", - "title": "string_16", + "title": "string_43", "runtime": 1, "policies": [], "config": {}, @@ -232,7 +234,7 @@ }, { "type": "object", - "title": "object_29", + "title": "object_56", "runtime": 1, "policies": [], "config": {}, @@ -247,7 +249,7 @@ }, { "type": "string", - "title": "string_18", + "title": "string_45", "runtime": 1, "policies": [], "config": {}, @@ -255,7 +257,7 @@ }, { "type": "list", - "title": "list_22", + "title": "list_49", "runtime": 1, "policies": [], "config": {}, @@ -264,7 +266,7 @@ }, { "type": "object", - "title": "object_21", + "title": "object_48", "runtime": 1, "policies": [], "config": {}, @@ -277,7 +279,7 @@ }, { "type": "string", - "title": "string_19", + "title": "string_46", "runtime": 1, "policies": [], "enum": [ @@ -290,7 +292,7 @@ }, { "type": "string", - "title": "string_20", + "title": "string_47", "runtime": 1, "policies": [], "config": {}, @@ -298,7 +300,7 @@ }, { "type": "list", - "title": "list_26", + "title": "list_53", "runtime": 1, "policies": [], "config": {}, @@ -307,7 +309,7 @@ }, { "type": "object", - "title": "object_25", + "title": "object_52", "runtime": 1, "policies": [], "config": {}, @@ -320,7 +322,7 @@ }, { "type": "string", - "title": "string_23", + "title": "string_50", "runtime": 1, "policies": [], "config": {}, @@ -328,7 +330,7 @@ }, { "type": "string", - "title": "string_24", + "title": "string_51", "runtime": 1, "policies": [], "config": {}, @@ -336,7 +338,7 @@ }, { "type": "optional", - "title": "optional_28", + "title": "optional_55", "runtime": 1, "policies": [], "config": {}, @@ -346,7 +348,7 @@ }, { "type": "string", - "title": "string_27", + "title": "string_54", "runtime": 1, "policies": [], "config": {}, @@ -355,7 +357,7 @@ }, { "type": "function", - "title": "func_39", + "title": "func_68", "runtime": 1, "policies": [ 0 @@ -370,7 +372,7 @@ }, { "type": "object", - "title": "object_33", + "title": "object_60", "runtime": 1, "policies": [], "config": {}, @@ -382,7 +384,7 @@ }, { "type": "list", - "title": "list_32", + "title": "list_59", "runtime": 1, "policies": [], "config": {}, @@ -391,7 +393,7 @@ }, { "type": "string", - "title": "string_31", + "title": "string_58", "runtime": 1, "policies": [], "config": {}, @@ -399,11 +401,277 @@ }, { "type": "boolean", - "title": "boolean_34", + "title": "boolean_61", + "runtime": 1, + "policies": [], + "config": {}, + "as_id": false + }, + { + "type": "function", + "title": "func_69", + "runtime": 1, + "policies": [ + 0 + ], + "config": {}, + "as_id": false, + "input": 38, + "output": 45, + "materializer": 6, + "rate_weight": null, + "rate_calls": true + }, + { + "type": "object", + "title": "object_12", + "runtime": 1, + "policies": [], + "config": {}, + "as_id": false, + "properties": { + "typegraph": 39, + "queryType": 40, + "fn": 41, + "argPaths": 42 + }, + "required": [] + }, + { + "type": "string", + "title": "string_8", + "runtime": 1, + "policies": [], + "config": {}, + "as_id": false + }, + { + "type": "string", + "title": "string_9", + "runtime": 1, + "policies": [], + "config": {}, + "as_id": false + }, + { + "type": "string", + "title": "string_10", + "runtime": 1, + "policies": [], + "config": {}, + "as_id": false + }, + { + "type": "list", + "title": "list_11", + "runtime": 1, + "policies": [], + "config": {}, + "as_id": false, + "items": 43 + }, + { + "type": "list", + "title": "list_7", + "runtime": 1, + "policies": [], + "config": {}, + "as_id": false, + "items": 44 + }, + { + "type": "string", + "title": "string_6", + "runtime": 1, + "policies": [], + "config": {}, + "as_id": false + }, + { + "type": "list", + "title": "_63_ArgInfoOut[]", + "runtime": 1, + "policies": [], + "config": {}, + "as_id": false, + "items": 46 + }, + { + "type": "object", + "title": "ArgInfoOut", + "runtime": 1, + "policies": [], + "config": {}, + "as_id": false, + "properties": { + "optional": 47, + "as_id": 48, + "title": 49, + "type": 50, + "enum": 51, + "runtime": 54, + "config": 55, + "default": 57, + "format": 59, + "fields": 61 + }, + "required": [] + }, + { + "type": "boolean", + "title": "boolean_13", + "runtime": 1, + "policies": [], + "config": {}, + "as_id": false + }, + { + "type": "boolean", + "title": "boolean_14", + "runtime": 1, + "policies": [], + "config": {}, + "as_id": false + }, + { + "type": "string", + "title": "string_15", + "runtime": 1, + "policies": [], + "config": {}, + "as_id": false + }, + { + "type": "string", + "title": "string_16", + "runtime": 1, + "policies": [], + "config": {}, + "as_id": false + }, + { + "type": "optional", + "title": "optional_19", + "runtime": 1, + "policies": [], + "config": {}, + "as_id": false, + "item": 52, + "default_value": null + }, + { + "type": "list", + "title": "list_18", + "runtime": 1, + "policies": [], + "config": {}, + "as_id": false, + "items": 53 + }, + { + "type": "string", + "title": "string_17", + "runtime": 1, + "policies": [], + "config": {}, + "as_id": false, + "format": "json" + }, + { + "type": "string", + "title": "string_20", "runtime": 1, "policies": [], "config": {}, "as_id": false + }, + { + "type": "optional", + "title": "optional_22", + "runtime": 1, + "policies": [], + "config": {}, + "as_id": false, + "item": 56, + "default_value": null + }, + { + "type": "string", + "title": "string_21", + "runtime": 1, + "policies": [], + "config": {}, + "as_id": false, + "format": "json" + }, + { + "type": "optional", + "title": "optional_24", + "runtime": 1, + "policies": [], + "config": {}, + "as_id": false, + "item": 58, + "default_value": null + }, + { + "type": "string", + "title": "string_23", + "runtime": 1, + "policies": [], + "config": {}, + "as_id": false, + "format": "json" + }, + { + "type": "optional", + "title": "optional_26", + "runtime": 1, + "policies": [], + "config": {}, + "as_id": false, + "item": 60, + "default_value": null + }, + { + "type": "string", + "title": "string_25", + "runtime": 1, + "policies": [], + "config": {}, + "as_id": false + }, + { + "type": "optional", + "title": "optional_30", + "runtime": 1, + "policies": [], + "config": {}, + "as_id": false, + "item": 62, + "default_value": null + }, + { + "type": "list", + "title": "list_29", + "runtime": 1, + "policies": [], + "config": {}, + "as_id": false, + "items": 63 + }, + { + "type": "object", + "title": "object_28", + "runtime": 1, + "policies": [], + "config": {}, + "as_id": false, + "properties": { + "subPath": 43, + "termNode": 46 + }, + "required": [] } ], "materializers": [ @@ -462,6 +730,15 @@ "idempotent": true }, "data": {} + }, + { + "name": "argInfoByPath", + "runtime": 1, + "effect": { + "effect": "read", + "idempotent": true + }, + "data": {} } ], "runtimes": [ diff --git a/typegate/src/typegraphs/typegate.py b/typegate/src/typegraphs/typegate.py index c5c70af05a..44a861df37 100644 --- a/typegate/src/typegraphs/typegate.py +++ b/typegate/src/typegraphs/typegate.py @@ -1,9 +1,7 @@ # Copyright Metatype under the Elastic License 2.0. from typegraph import Graph, fx, t, typegraph -from typegraph.gen.exports.runtimes import ( - TypegateOperation, -) +from typegraph.gen.exports.runtimes import TypegateOperation from typegraph.gen.types import Err from typegraph.graph.params import Auth, Cors, Rate from typegraph.runtimes.base import Materializer @@ -72,6 +70,14 @@ def typegate(g: Graph): serialized_typegraph_mat_id.value, effect=fx.read() ) + arg_info_by_path_id = runtimes.register_typegate_materializer( + store, + TypegateOperation.GET_ARG_INFO_BY_PATH, + ) + if isinstance(arg_info_by_path_id, Err): + raise Exception(arg_info_by_path_id.value) + arg_info_by_path_mat = Materializer(arg_info_by_path_id.value, effect=fx.read()) + serialized = t.gen(t.string(), serialized_typegraph_mat) typegraph = t.struct( @@ -81,6 +87,32 @@ def typegate(g: Graph): }, name="Typegraph", ) + path = t.list(t.string()) + arg_info_inp = t.struct( + { + "typegraph": t.string(), + "queryType": t.string(), + "fn": t.string(), + "argPaths": t.list(path), + } + ) + + arg_info_out = t.struct( + { + "optional": t.boolean(), + "as_id": t.boolean(), + "title": t.string(), + "type": t.string(), + "enum": t.list(t.json()).optional(), + "runtime": t.string(), + "config": t.json().optional(), + "default": t.json().optional(), + "format": t.string().optional(), + "fields": t.list( + t.struct({"subPath": path, "termNode": g.ref("ArgInfoOut")}) + ).optional(), + } + ).rename("ArgInfoOut") g.expose( typegraphs=t.func( @@ -130,5 +162,8 @@ def typegate(g: Graph): remove_typegraphs_mat, rate_calls=True, ), + argInfoByPath=t.func( + arg_info_inp, t.list(arg_info_out), arg_info_by_path_mat, rate_calls=True + ), default_policy=admin_only, ) diff --git a/typegate/tests/e2e/cli/deploy1_test.ts b/typegate/tests/e2e/cli/deploy1_test.ts deleted file mode 100644 index 1ca72057ed..0000000000 --- a/typegate/tests/e2e/cli/deploy1_test.ts +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright Metatype OÜ, licensed under the Elastic License 2.0. -// SPDX-License-Identifier: Elastic-2.0 - -import { gql, Meta } from "test-utils/mod.ts"; -import { TestModule } from "test-utils/test_module.ts"; -import { removeMigrations } from "test-utils/migrations.ts"; -import { assertStringIncludes } from "std/assert/mod.ts"; -import pg from "npm:pg"; - -const m = new TestModule(import.meta); - -const tgName = "migration-failure-test"; - -/** - * These tests use different ports for the virtual typegate instance to avoid - * conflicts with one another when running in parallel. - */ - -async function writeTypegraph(version: number | null) { - if (version == null) { - await m.shell([ - "bash", - "-c", - "cp ./templates/migration.py migration.py", - ]); - } else { - await m.shell([ - "bash", - "select.sh", - "templates/migration.py", - `${version}`, - "migration.py", - ]); - } -} - -async function deploy(port: number | null, noMigration = false) { - const migrationOpts = noMigration ? [] : ["--create-migration"]; - - try { - const out = await m.cli( - {}, - "deploy", - "--target", - port == null ? "dev" : `dev${port}`, - "-f", - "migration.py", - "--allow-dirty", - ...migrationOpts, - "--allow-destructive", - ); - if (out.stdout.length > 0) { - console.log( - `-- deploy STDOUT start --\n${out.stdout}-- deploy STDOUT end --`, - ); - } - if (out.stderr.length > 0) { - console.log( - `-- deploy STDERR start --\n${out.stderr}-- deploy STDERR end --`, - ); - } - } catch (e) { - console.log(e.toString()); - throw e; - } -} - -async function reset(schema: string) { - await removeMigrations(tgName); - - // remove the database schema - const client = new pg.Client({ - connectionString: "postgres://postgres:password@localhost:5432/db", - }); - await client.connect(); - await client.query(`DROP SCHEMA IF EXISTS ${schema} CASCADE`); - await client.end(); -} - -Meta.test( - "meta deploy: fails migration for new columns without default value", - async (t) => { - await t.should("load first version of the typegraph", async () => { - await reset("e2e7895alt"); - await writeTypegraph(null); - }); - - const port = 7895; - - // `deploy` must be run outside of the `should` block, - // otherwise this would fail by leaking ops. - // That is expected since it creates new engine that persists beyond the - // `should` block. - await deploy(port); - - await t.should("insert records", async () => { - const e = t.getTypegraphEngine(tgName); - if (!e) { - throw new Error("typegraph not found"); - } - await gql` - mutation { - createRecord(data: {}) { - id - } - } - ` - .expectData({ - createRecord: { - id: 1, - }, - }) - .on(e); - }); - - await t.should("load second version of the typegraph", async () => { - await writeTypegraph(1); - }); - - try { - await deploy(port); - } catch (e) { - assertStringIncludes( - e.message, - 'column "age" of relation "Record" contains null values: set a default value:', - ); - } - }, - { port: 7895, systemTypegraphs: true }, -); diff --git a/typegate/tests/e2e/cli/undeploy_test.ts b/typegate/tests/e2e/cli/undeploy_test.ts index bc278cd324..4d1ab98eac 100644 --- a/typegate/tests/e2e/cli/undeploy_test.ts +++ b/typegate/tests/e2e/cli/undeploy_test.ts @@ -7,43 +7,31 @@ import { dropSchema } from "test-utils/database.ts"; const m = new TestModule(import.meta); +const port = 7897; + Meta.test("meta undeploy", async (t) => { // prepare - await dropSchema("e2e7895alt"); - - // TODO assert this leaks resources - // await t.should("fail", async () => { - // const _out = await m.cli( - // {}, - // "deploy", - // "--target", - // "dev7895", - // "-f", - // "templates/migration.py", - // "--allow-dirty", - // ); - // }); + await dropSchema(`e2e${port}alt`); + // no leaked resources error await t.should("free resources", async () => { - const _out = await m.cli( + await m.cli( {}, "deploy", "--target", - "dev7895", + `dev${port}`, "-f", "templates/migration.py", "--allow-dirty", ); - const out2 = await m.cli( + await m.cli( {}, "undeploy", "--target", - "dev7895", + `dev${port}`, "--typegraph", "migration-failure-test", ); - - console.log(out2.stderr); }); -}, { port: 7895, systemTypegraphs: true }); +}, { port, systemTypegraphs: true }); diff --git a/typegate/tests/e2e/typegraph/validator_test.ts b/typegate/tests/e2e/typegraph/validator_test.ts index 74236b5c78..2b8b933ffe 100644 --- a/typegate/tests/e2e/typegraph/validator_test.ts +++ b/typegate/tests/e2e/typegraph/validator_test.ts @@ -3,7 +3,7 @@ import { TestModule } from "test-utils/test_module.ts"; import { Meta } from "test-utils/mod.ts"; -import { assert } from "std/assert/mod.ts"; +import { fail } from "std/assert/mod.ts"; const m = new TestModule(import.meta); @@ -22,7 +22,7 @@ Meta.test("typegraph validation", async (t) => { "-f", "validator.py", ); - assert(false, "should have thrown"); + fail("should have thrown"); } catch (e) { await t.assertSnapshot(e.stderr); } diff --git a/typegraph/core/src/runtimes/mod.rs b/typegraph/core/src/runtimes/mod.rs index 15167db004..e7d47ce349 100644 --- a/typegraph/core/src/runtimes/mod.rs +++ b/typegraph/core/src/runtimes/mod.rs @@ -583,6 +583,7 @@ impl crate::wit::runtimes::Guest for crate::Lib { WitOp::AddTypegraph => (WitEffect::Create(true), Op::AddTypegraph), WitOp::RemoveTypegraphs => (WitEffect::Delete(true), Op::RemoveTypegraphs), WitOp::GetSerializedTypegraph => (WitEffect::Read, Op::GetSerializedTypegraph), + WitOp::GetArgInfoByPath => (WitEffect::Read, Op::GetArgInfoByPath), }; Ok(Store::register_materializer(Materializer::typegate( diff --git a/typegraph/core/src/runtimes/typegate.rs b/typegraph/core/src/runtimes/typegate.rs index 0c4c35fda3..54a0cbaf41 100644 --- a/typegraph/core/src/runtimes/typegate.rs +++ b/typegraph/core/src/runtimes/typegate.rs @@ -16,6 +16,7 @@ pub enum TypegateOperation { AddTypegraph, RemoveTypegraphs, GetSerializedTypegraph, + GetArgInfoByPath, } impl MaterializerConverter for TypegateOperation { @@ -34,6 +35,7 @@ impl MaterializerConverter for TypegateOperation { Self::AddTypegraph => "addTypegraph", Self::RemoveTypegraphs => "removeTypegraphs", Self::GetSerializedTypegraph => "serializedTypegraph", + Self::GetArgInfoByPath => "argInfoByPath", } .to_string(), runtime, diff --git a/typegraph/core/wit/typegraph.wit b/typegraph/core/wit/typegraph.wit index ff91e598f8..f3c0c9d1e5 100644 --- a/typegraph/core/wit/typegraph.wit +++ b/typegraph/core/wit/typegraph.wit @@ -394,6 +394,7 @@ interface runtimes { add-typegraph, remove-typegraphs, get-serialized-typegraph, + get-arg-info-by-path } register-typegate-materializer: func(operation: typegate-operation) -> result