diff --git a/src/typegate/src/runtimes/substantial.ts b/src/typegate/src/runtimes/substantial.ts index 35c366b0d..a3bc5596b 100644 --- a/src/typegate/src/runtimes/substantial.ts +++ b/src/typegate/src/runtimes/substantial.ts @@ -218,9 +218,11 @@ export class SubstantialRuntime extends Runtime { return JSON.parse(JSON.stringify(res)); }; case "results": - return this.#resultsResover(false); + return this.#resultsResolver(false); case "results_raw": - return this.#resultsResover(true); + return this.#resultsResolver(true); + case "advanced_filters": + return this.#advancedFiltersResolver(); case "internal_link_parent_child": return this.#linkerResolver(); default: @@ -285,7 +287,7 @@ export class SubstantialRuntime extends Runtime { }; } - #resultsResover(enableGenerics: boolean): Resolver { + #resultsResolver(enableGenerics: boolean): Resolver { return async ({ name: workflowName }) => { this.#checkWorkflowExistOrThrow(workflowName); @@ -407,6 +409,24 @@ export class SubstantialRuntime extends Runtime { }; } + #advancedFiltersResolver(): Resolver { + return ({ filter }) => { + console.log("Filter", filter); + + const dummySearchResult = { + run_id: "fake", + started_at: new Date().toJSON(), + ended_at: new Date().toJSON(), + status: "COMPLETED", + value: JSON.stringify(filter) + }; + + return [ + dummySearchResult + ]; + }; + } + #linkerResolver(): Resolver { return async ({ parent_run_id, child_run_id }) => { await Meta.substantial.metadataWriteParentChildLink({ diff --git a/src/typegate/src/runtimes/substantial/deno_context.ts b/src/typegate/src/runtimes/substantial/deno_context.ts index 302ad8ce8..73090e156 100644 --- a/src/typegate/src/runtimes/substantial/deno_context.ts +++ b/src/typegate/src/runtimes/substantial/deno_context.ts @@ -440,7 +440,10 @@ class SubLogger { const message = args.map((arg) => { try { - return JSON.stringify(arg); + const json = JSON.stringify(arg); + // Functions are omitted, + // For example, JSON.stringify(() => 1234) => undefined (no throw) + return json === undefined ? String(arg) : json; } catch(_) { return String(arg); } diff --git a/src/typegraph/core/src/runtimes/substantial.rs b/src/typegraph/core/src/runtimes/substantial/mod.rs similarity index 77% rename from src/typegraph/core/src/runtimes/substantial.rs rename to src/typegraph/core/src/runtimes/substantial/mod.rs index 23a824553..e71fb58ea 100644 --- a/src/typegraph/core/src/runtimes/substantial.rs +++ b/src/typegraph/core/src/runtimes/substantial/mod.rs @@ -6,7 +6,6 @@ use crate::errors::Result; use crate::global_store::Store; use crate::t::{self, TypeBuilder}; use crate::typegraph::TypegraphContext; -use crate::types::WithRuntimeConfig; use crate::wit::core::FuncParams; use crate::wit::{ core::RuntimeId, runtimes::Effect as WitEffect, runtimes::SubstantialOperationData, @@ -14,6 +13,8 @@ use crate::wit::{ use common::typegraph::Materializer; use serde_json::json; +mod type_utils; + #[derive(Debug)] pub enum SubstantialMaterializer { Start { secrets: Vec }, @@ -25,6 +26,7 @@ pub enum SubstantialMaterializer { Results, ResultsRaw, InternalLinkParentChild, + AdvancedFilters, } impl MaterializerConverter for SubstantialMaterializer { @@ -55,6 +57,7 @@ impl MaterializerConverter for SubstantialMaterializer { SubstantialMaterializer::InternalLinkParentChild => { ("internal_link_parent_child".to_string(), json!({})) } + SubstantialMaterializer::AdvancedFilters => ("advanced_filters".to_string(), json!({})), }; Ok(Materializer { @@ -70,9 +73,9 @@ pub fn substantial_operation( runtime: RuntimeId, data: SubstantialOperationData, ) -> Result { - let mut inp = t::struct_(); - let (effect, mat_data, out_ty) = match data { + let (effect, mat_data, inp_ty, out_ty) = match data { SubstantialOperationData::Start(data) => { + let mut inp = t::struct_(); inp.prop("name", t::string().build()?); inp.prop( "kwargs", @@ -86,27 +89,32 @@ pub fn substantial_operation( SubstantialMaterializer::Start { secrets: data.secrets, }, + inp.build()?, t::string().build()?, ) } SubstantialOperationData::StartRaw(data) => { + let mut inp = t::struct_(); inp.prop("name", t::string().build()?); - inp.prop("kwargs", t_json_string()?.into()); + inp.prop("kwargs", t::json_str()?); ( WitEffect::Create(true), SubstantialMaterializer::StartRaw { secrets: data.secrets, }, + inp.build()?, t::string().build()?, ) } SubstantialOperationData::Stop => { + let mut inp = t::struct_(); inp.prop("run_id", t::string().build()?); ( WitEffect::Create(false), SubstantialMaterializer::Stop, + inp.build()?, t::list(t::string().build()?).build()?, ) } @@ -116,31 +124,36 @@ pub fn substantial_operation( .prop("payload", data.into()) .build()?; + let mut inp = t::struct_(); inp.prop("run_id", t::string().build()?); inp.prop("event", event); ( WitEffect::Create(false), SubstantialMaterializer::Send, + inp.build()?, t::string().build()?, ) } SubstantialOperationData::SendRaw => { let event = t::struct_() .prop("name", t::string().build()?) - .prop("payload", t_json_string()?.into()) + .prop("payload", t::json_str()?) .build()?; + let mut inp = t::struct_(); inp.prop("run_id", t::string().build()?); inp.prop("event", event); ( WitEffect::Create(false), SubstantialMaterializer::SendRaw, + inp.build()?, t::string().build()?, ) } SubstantialOperationData::Resources => { + let mut inp = t::struct_(); inp.prop("name", t::string().build()?); let row = t::struct_() @@ -157,96 +170,58 @@ pub fn substantial_operation( .prop("running", t::list(row).build()?) .build()?; - (WitEffect::Read, SubstantialMaterializer::Resources, out) + ( + WitEffect::Read, + SubstantialMaterializer::Resources, + inp.build()?, + out, + ) } SubstantialOperationData::Results(data) => { + let mut inp = t::struct_(); inp.prop("name", t::string().build()?); ( WitEffect::Read, SubstantialMaterializer::Results, - results_op_results_ty(data)?, + inp.build()?, + type_utils::results_op_results_ty(data)?, ) } SubstantialOperationData::ResultsRaw => { + let mut inp = t::struct_(); inp.prop("name", t::string().build()?); ( WitEffect::Read, SubstantialMaterializer::ResultsRaw, - results_op_results_ty(t_json_string()?)?, + inp.build()?, + type_utils::results_op_results_ty(t::json_str()?.into())?, ) } SubstantialOperationData::InternalLinkParentChild => { + let mut inp = t::struct_(); inp.prop("parent_run_id", t::string().build()?); inp.prop("child_run_id", t::string().build()?); ( WitEffect::Create(true), SubstantialMaterializer::InternalLinkParentChild, + inp.build()?, t::boolean().build()?, ) } + SubstantialOperationData::AdvancedFilters => ( + WitEffect::Read, + SubstantialMaterializer::AdvancedFilters, + type_utils::filter_expr_ty()?, + type_utils::search_results_ty()?, + ), }; let mat = super::Materializer::substantial(runtime, mat_data, effect); let mat_id = Store::register_materializer(mat); Ok(FuncParams { - inp: inp.build()?.into(), + inp: inp_ty.into(), out: out_ty.into(), mat: mat_id, }) } - -fn t_json_string() -> Result { - t::string() - .build() - .and_then(|r| { - r.with_config(json!({ - "format": "json" - })) - }) - .map(|r| r.id().into()) -} - -fn results_op_results_ty(out: u32) -> Result { - let count = t::integer().build()?; - - let result = t::struct_() - .prop("status", t::string().build()?) - .prop("value", t::optional(out.into()).build()?) - .build()?; - - let ongoing_runs = t::list( - t::struct_() - .prop("run_id", t::string().build()?) - .prop("started_at", t::string().build()?) - .build()?, - ) - .build()?; - - let completed_runs = t::list( - t::struct_() - .prop("run_id", t::string().build()?) - .prop("started_at", t::string().build()?) - .prop("ended_at", t::string().build()?) - .prop("result", result) - .build()?, - ) - .build()?; - - t::struct_() - .prop( - "ongoing", - t::struct_() - .prop("count", count) - .prop("runs", ongoing_runs) - .build()?, - ) - .prop( - "completed", - t::struct_() - .prop("count", count) - .prop("runs", completed_runs) - .build()?, - ) - .build() -} diff --git a/src/typegraph/core/src/runtimes/substantial/type_utils.rs b/src/typegraph/core/src/runtimes/substantial/type_utils.rs new file mode 100644 index 000000000..c089e86bc --- /dev/null +++ b/src/typegraph/core/src/runtimes/substantial/type_utils.rs @@ -0,0 +1,132 @@ +// Copyright Metatype OÜ, licensed under the Mozilla Public License Version 2.0. +// SPDX-License-Identifier: MPL-2.0 + +use crate::errors::Result; +use crate::t::{self, TypeBuilder}; + +pub fn results_op_results_ty(out: u32) -> Result { + let count = t::integer().build()?; + + let result = t::struct_() + .prop("status", t::string().build()?) + .prop("value", t::optional(out.into()).build()?) + .build()?; + + let ongoing_runs = t::list( + t::struct_() + .prop("run_id", t::string().build()?) + .prop("started_at", t::string().build()?) + .build()?, + ) + .build()?; + + let completed_runs = t::list( + t::struct_() + .prop("run_id", t::string().build()?) + .prop("started_at", t::string().build()?) + .prop("ended_at", t::string().build()?) + .prop("result", result) + .build()?, + ) + .build()?; + + t::struct_() + .prop( + "ongoing", + t::struct_() + .prop("count", count) + .prop("runs", ongoing_runs) + .build()?, + ) + .prop( + "completed", + t::struct_() + .prop("count", count) + .prop("runs", completed_runs) + .build()?, + ) + .build() +} + +/// Term `{ op: value } | { special: { op: value } }` +/// * op: "eq", "lt", "contains", .. +/// * special: "started_at", "status", .. +fn filter_term_variants() -> Result> { + // FIXME: a generic json would have been helpful here vs json string + // let any = t::eitherx!(t::integer(), t::string(), t::boolean(), ...).build_named("AnyValue")?; + let value_to_comp_against = t::json_str()?; + let ops = ["eq", "lt", "lte", "gt", "gte", "in", "contains"] + .into_iter() + .map(|op| t::struct_().prop(op, value_to_comp_against).build()) + .collect::>>()?; + + let op_value = t::either(ops.clone().into_iter()).build()?; + let special = ["started_at", "ended_at", "status"] + .into_iter() + .map(|sp| t::struct_().prop(sp, op_value).build()) + .collect::>>()?; + + let mut variants = vec![]; + variants.extend(ops.iter()); + variants.extend(special.iter()); + + Ok(variants) +} + +/// Expr `{ op: [...] }` +/// * op: "and" or "or" +/// * ...: may contain itself, or a term +pub fn filter_expr_ty() -> Result { + let mut and = t::struct_(); + let mut or = t::struct_(); + + let op_term_variants = filter_term_variants()?; + + let mut expr = t::eitherx!(and, or); + expr.data + .variants + .extend(op_term_variants.into_iter().map(|ty| { + let id: u32 = ty.into(); + id + })); + + let expr_id = expr.build()?; + + and.prop("and", t::listx(expr_id).build()?); + or.prop("or", t::listx(expr_id).build()?); + + /* + query { + search(filter:{ + and: [ + { started_at: { eq: "a" } }, + { status: { contains: "STO" } }, + { in: "abc" } + # { or: []} # FIXME: broken ref + ] + }) { + started_at + ended_at + status + value + } + } + + */ + t::struct_() + .prop("filter", t::eitherx!(expr_id, and, or).build()?) + .build() +} + +pub fn search_results_ty() -> Result { + t::list( + t::struct_() + .prop("run_id", t::string().build()?) + .prop("started_at", t::string().build()?) + .prop("ended_at", t::string().build()?) + .prop("status", t::string().build()?) + .prop("value", t::json_str()?) + .build()?, + ) + .build() +} diff --git a/src/typegraph/core/src/t.rs b/src/typegraph/core/src/t.rs index 40e06b1fa..4bdcafbc7 100644 --- a/src/typegraph/core/src/t.rs +++ b/src/typegraph/core/src/t.rs @@ -5,6 +5,7 @@ use crate::errors::Result; use crate::errors::TgError; use crate::types::RefAttr; use crate::types::TypeRefBuilder; +use crate::types::WithRuntimeConfig; use crate::types::{Named as _, TypeId, TypeRef}; use crate::wit::core::{ @@ -334,11 +335,12 @@ macro_rules! unionx { crate::t::unionx![$($ty),*] }; } +use serde_json::json; pub(crate) use unionx; #[derive(Default)] pub struct EitherBuilder { - data: TypeEither, + pub data: TypeEither, } #[allow(clippy::derivable_impls)] @@ -391,6 +393,17 @@ pub fn struct_() -> StructBuilder { Default::default() } +pub fn json_str() -> Result { + string() + .build() + .and_then(|r| { + r.with_config(json!({ + "format": "json" + })) + }) + .map(|r| r.id()) +} + pub fn struct_extends(ty: TypeId) -> Result { Ok(StructBuilder { data: TypeStruct { diff --git a/src/typegraph/core/wit/typegraph.wit b/src/typegraph/core/wit/typegraph.wit index 763ce9b2f..cda10f336 100644 --- a/src/typegraph/core/wit/typegraph.wit +++ b/src/typegraph/core/wit/typegraph.wit @@ -529,7 +529,9 @@ interface runtimes { // type of result results(type-id), results-raw, - internal-link-parent-child + internal-link-parent-child, + // filters + advanced-filters } register-substantial-runtime: func(data: substantial-runtime-data) -> result; diff --git a/src/typegraph/deno/src/runtimes/substantial.ts b/src/typegraph/deno/src/runtimes/substantial.ts index 7e9c81f85..9d2dc1259 100644 --- a/src/typegraph/deno/src/runtimes/substantial.ts +++ b/src/typegraph/deno/src/runtimes/substantial.ts @@ -100,6 +100,12 @@ export class SubstantialRuntime extends Runtime { }); } + advancedFilters(): Func { + return this._genericSubstantialFunc({ + tag: "advanced-filters" + }); + } + #internalLinkParentChild(): Func { return this._genericSubstantialFunc({ tag: "internal-link-parent-child", diff --git a/src/typegraph/python/typegraph/runtimes/substantial.py b/src/typegraph/python/typegraph/runtimes/substantial.py index f0cb7a80c..ecd9ab164 100644 --- a/src/typegraph/python/typegraph/runtimes/substantial.py +++ b/src/typegraph/python/typegraph/runtimes/substantial.py @@ -19,6 +19,7 @@ SubstantialOperationDataStart, SubstantialOperationDataStartRaw, SubstantialOperationDataStop, + SubstantialOperationDataAdvancedFilters, SubstantialRuntimeData, SubstantialStartData, WorkflowFileDescription, @@ -85,6 +86,9 @@ def query_results(self, output: "t.typedef"): def query_results_raw(self): return self._generic_substantial_func(SubstantialOperationDataResultsRaw()) + def advanced_filters(self): + return self._generic_substantial_func(SubstantialOperationDataAdvancedFilters()) + def _internal_link_parent_child(self): return self._generic_substantial_func( SubstantialOperationDataInternalLinkParentChild()