Skip to content

Commit

Permalink
Merge pull request #79 from unyt-org/add-remote-static-properties
Browse files Browse the repository at this point in the history
Add remote static properties
  • Loading branch information
benStre authored Feb 10, 2024
2 parents 6144f69 + a0a9ee1 commit bbbabab
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 16 deletions.
48 changes: 40 additions & 8 deletions docs/manual/08 Public Endpoint Interfaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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.
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")`
```
6 changes: 5 additions & 1 deletion js_adapter/js_class_adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 <Function>

static_scope.setVariable(name, dx_function); // add <Function> to static scope
Expand Down
3 changes: 3 additions & 0 deletions runtime/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
30 changes: 23 additions & 7 deletions types/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";



Expand Down Expand Up @@ -455,23 +456,38 @@ export class Function<T extends (...args: any) => any = (...args: any) => any> e
* add to() extension method to functions
*/

type Receiver = target_clause|string|Iterable<Target|string>

declare global {
interface CallableFunction {
to<T, A extends unknown[], R>(this: (this: T, ...args: A) => R, receiver?:string): (...args: A)=>Promise<Awaited<Promise<R>>>;
to<T, A extends unknown[], R>(this: (this: T, ...args: A) => R, receiver?:Receiver): (...args: A)=>Promise<Awaited<Promise<R>>>;
}
}

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<Target|endpoint_name>)].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];
return datex(`#public.?.? ?`, [externalScopeName, externalFunctionName, new Tuple(argArray)], receivers as target_clause)
}
})
}

// @ts-ignore override function prototype
Expand Down

0 comments on commit bbbabab

Please sign in to comment.