From 7f071412322f9ed55a12e2eab2ed1fd5185e3204 Mon Sep 17 00:00:00 2001 From: benStre Date: Fri, 9 Feb 2024 23:38:30 +0100 Subject: [PATCH 1/2] add support for calling @property methods remotely --- docs/manual/08 Public Endpoint Interfaces.md | 48 ++++++++++++++++---- js_adapter/js_class_adapter.ts | 6 ++- runtime/constants.ts | 3 ++ types/function.ts | 31 ++++++++++--- 4 files changed, 72 insertions(+), 16 deletions(-) diff --git a/docs/manual/08 Public Endpoint Interfaces.md b/docs/manual/08 Public Endpoint Interfaces.md index 6de3de91..1e5bbbae 100644 --- a/docs/manual/08 Public Endpoint Interfaces.md +++ b/docs/manual/08 Public Endpoint Interfaces.md @@ -17,13 +17,6 @@ await Datex.Supranet.connect(); } ``` -Other endpoints can call this function by accessing the `MyAPI` as an endpoint property: - -```ts -// assuming the endpoint running my-api.ts is @example -// call exampleFunction and get the return value -const result = await datex `@example.MyAPI.exampleFunction(1.5, "xyz")` -``` Inside a public function (like in any function), the [`datex.meta` property](./08%20The%20DATEX%20API.md) can be used to find out which endpoint called the function: @@ -46,4 +39,43 @@ const admin = f `@exampleAdmin` } ``` -This can be used to restrict permissions for certain functionalities to specific endpoints or implement rate limiting. \ No newline at end of file +This can be used to restrict permissions for certain functionalities to specific endpoints or implement rate limiting. + + + +## Calling public functions on remote endpoints + +Methods defined in a public endpoint interface class can be called on other endpoints that also implement +the interface. +To specify the receivers, chain a `.to()` method call together with the actual method call: + +```ts + +// call locally +const result1 = await MyAPI.exampleFunction(42, 'xyz'); + +// call on @example +const result2 = await MyAPI.exampleFunction.to('@example')(42, 'xyz'); +``` + +You can call the function on multiple endpoint at once by passing an array or set of `Endpoint` objects +or endpoint identifier to the `to()` call.: + +```ts +// call on @example1 and @example2 +const result3 = await MyAPI.exampleFunction.to(['@example1', '@example2'])(42, 'xyz'); +``` + + +> [!WARNING] +> When calling a function on multiple endpoints in a single call, +> only the first received response is returned (similar to Promise.race). + + +Altenatively, you can access a public interface directly with DATEX Script code: + +```ts +// assuming the endpoint running my-api.ts is @example +// call exampleFunction and get the return value +const result = await datex `@example.MyAPI.exampleFunction(1.5, "xyz")` +``` diff --git a/js_adapter/js_class_adapter.ts b/js_adapter/js_class_adapter.ts index 9d92b80c..caf65f88 100644 --- a/js_adapter/js_class_adapter.ts +++ b/js_adapter/js_class_adapter.ts @@ -23,7 +23,7 @@ import { Error as DatexError, ValueError } from "../types/errors.ts"; import { Function as DatexFunction } from "../types/function.ts"; import { DatexObject } from "../types/object.ts"; import { Tuple } from "../types/tuple.ts"; -import { DX_PERMISSIONS, DX_TYPE, DX_ROOT, INIT_PROPS } from "../runtime/constants.ts"; +import { DX_PERMISSIONS, DX_TYPE, DX_ROOT, INIT_PROPS, DX_EXTERNAL_SCOPE_NAME, DX_EXTERNAL_FUNCTION_NAME } from "../runtime/constants.ts"; import { type Class } from "../utils/global_types.ts"; import { Conjunction, Disjunction, Logical } from "../types/logic.ts"; import { client_type } from "../utils/constants.ts"; @@ -662,6 +662,10 @@ function exposeStaticClass(original_class:Class, data:class_data) { // set allowed endpoints for this method //static_scope.setAllowedEndpointsForProperty(name, this.method_a_filters.get(name)) + const fn = original_class[name]; + fn[DX_EXTERNAL_SCOPE_NAME] = static_scope.name; + fn[DX_EXTERNAL_FUNCTION_NAME] = exposed_public[name] + const dx_function = Pointer.proxifyValue(DatexFunction.createFromJSFunction(current_value, original_class, name), true, undefined, false, true) ; // generate static_scope.setVariable(name, dx_function); // add to static scope diff --git a/runtime/constants.ts b/runtime/constants.ts index 14849425..434e6922 100644 --- a/runtime/constants.ts +++ b/runtime/constants.ts @@ -31,6 +31,9 @@ export const DX_BOUND_LOCAL_SLOT: unique symbol = Symbol("DX_BOUND_SLOT"); // lo // ------------------------------------- export const DX_SLOTS: unique symbol = Symbol("DX_SLOTS"); +export const DX_EXTERNAL_SCOPE_NAME: unique symbol = Symbol("DX_EXTERNAL_SCOPE_NAME"); // string name of the external scope +export const DX_EXTERNAL_FUNCTION_NAME: unique symbol = Symbol("DX_EXTERNAL_FUNCTION_NAME"); // string name for an external function + export const SLOT_WRITE = 0xfef0; export const SLOT_READ = 0xfef1; export const SLOT_EXEC = 0xfef2; diff --git a/types/function.ts b/types/function.ts index 90ec509b..92952167 100644 --- a/types/function.ts +++ b/types/function.ts @@ -11,11 +11,12 @@ import { Compiler } from "../compiler/compiler.ts"; import { Stream } from "./stream.ts" import { PermissionError, RuntimeError, TypeError, ValueError } from "./errors.ts"; import { ProtocolDataType } from "../compiler/protocol_types.ts"; -import { VOID } from "../runtime/constants.ts"; +import { DX_EXTERNAL_FUNCTION_NAME, DX_EXTERNAL_SCOPE_NAME, VOID } from "../runtime/constants.ts"; import { Type, type_clause } from "./type.ts"; import { callWithMetadata, callWithMetadataAsync, getMeta } from "../utils/caller_metadata.ts"; import { Datex } from "../mod.ts"; import { Callable, ExtensibleFunction, getDeclaredExternalVariables, getDeclaredExternalVariablesAsync, getSourceWithoutUsingDeclaration } from "./function-utils.ts"; +import { Conjunction, Disjunction, Target } from "../datex_all.ts"; @@ -455,23 +456,39 @@ export class Function any = (...args: any) => any> e * add to() extension method to functions */ +type Receiver = target_clause|string|Iterable + declare global { interface CallableFunction { - to(this: (this: T, ...args: A) => R, receiver?:string): (...args: A)=>Promise>>; + to(this: (this: T, ...args: A) => R, receiver?:Receiver): (...args: A)=>Promise>>; } } -function to (receiver:endpoint_name):any -function to (receivers:target_clause):any -function to (this:any, receivers:target_clause|endpoint_name) { +function to (this:Function, receivers:Receiver) { + if ( + receivers + && typeof receivers == "object" + && typeof (receivers as any)[Symbol.iterator] === 'function' + && !(receivers instanceof Conjunction) + && !(receivers instanceof Disjunction) + ) { + receivers = new Disjunction(...[...(receivers as Iterable)].map(r => r instanceof Target ? r : Target.get(r as endpoint_name))); + } if (this instanceof Function) { return new Proxy(this, { - apply: (_target, thisArg, argArray:any[]) => { + apply: (_target, thisArg, argArray:unknown[]) => { return (_target as any).apply(thisArg, argArray) // TODO: inject receivers } }) } - else throw new Error("This function has no bindings for external calls, .to() cannot be used.") + else return new Proxy(this, { + apply: (target, _thisArg, argArray:unknown[]) => { + const externalScopeName = target[DX_EXTERNAL_SCOPE_NAME]; + const externalFunctionName = target[DX_EXTERNAL_FUNCTION_NAME]; + console.log("call",externalScopeName, externalFunctionName, argArray) + return datex(`#public.?.? ?`, [externalScopeName, externalFunctionName, new Tuple(argArray)], receivers as target_clause) + } + }) } // @ts-ignore override function prototype From a0a9ee1fbb23b40f502cca6f179ab4101004181a Mon Sep 17 00:00:00 2001 From: benStre Date: Sat, 10 Feb 2024 02:00:20 +0100 Subject: [PATCH 2/2] remove debug log --- types/function.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/types/function.ts b/types/function.ts index 92952167..77c57dd3 100644 --- a/types/function.ts +++ b/types/function.ts @@ -485,7 +485,6 @@ function to (this:Function, receivers:Receiver) { apply: (target, _thisArg, argArray:unknown[]) => { const externalScopeName = target[DX_EXTERNAL_SCOPE_NAME]; const externalFunctionName = target[DX_EXTERNAL_FUNCTION_NAME]; - console.log("call",externalScopeName, externalFunctionName, argArray) return datex(`#public.?.? ?`, [externalScopeName, externalFunctionName, new Tuple(argArray)], receivers as target_clause) } })