Skip to content

Commit

Permalink
Merge branch 'main' into fix-bigint-overflow
Browse files Browse the repository at this point in the history
  • Loading branch information
benStre authored Jan 21, 2024
2 parents 2c952a0 + 81c8c24 commit be20156
Show file tree
Hide file tree
Showing 22 changed files with 335 additions and 117 deletions.
50 changes: 50 additions & 0 deletions .github/workflows/update-prs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Refresh PRs
on:
push:
branches:
- main

permissions:
pull-requests: write

jobs:
trigger-pr-update:
runs-on: ubuntu-latest

steps:
- uses: actions/github-script@v7
with:
script: |
// Get a list of all issues created by the PR opener
// See: https://octokit.github.io/rest.js/#pagination
const creator = context.payload.sender.login
const prs = await github.request('GET /repos/{owner}/{repo}/pulls', {
owner: context.repo.owner,
repo: context.repo.repo,
headers: {
'X-GitHub-Api-Version': '2022-11-28'
}
})
for (const pr of prs.data) {
if (pr.state !== 'open') continue;
console.log("Refreshing PR diff for #" + pr.number + " (" + pr.title + ")");
await github.request('PATCH /repos/{owner}/{repo}/pulls/{pull_number}', {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
base: 'develop',
headers: {
'X-GitHub-Api-Version': '2022-11-28'
}
})
await github.request('PATCH /repos/{owner}/{repo}/pulls/{pull_number}', {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
base: 'main',
headers: {
'X-GitHub-Api-Version': '2022-11-28'
}
})
}
8 changes: 6 additions & 2 deletions compiler/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2186,8 +2186,12 @@ export class Compiler {
const pointer_origin = (id_buffer[0]==BinaryCode.ENDPOINT || id_buffer[0]==BinaryCode.PERSON_ALIAS || id_buffer[0]==BinaryCode.INSTITUTION_ALIAS) ? <IdEndpoint> Target.get(id_buffer.slice(1,19), id_buffer.slice(19,21), id_buffer[0]) : null;

const singleReceiver =
(SCOPE.options.to instanceof Endpoint && SCOPE.options.to) ||
(SCOPE.options.to instanceof Disjunction && SCOPE.options.to.size == 1 && [...SCOPE.options.to][0] instanceof Endpoint && [...SCOPE.options.to][0])
SCOPE.options.to instanceof Endpoint ||
(
SCOPE.options.to instanceof Disjunction &&
SCOPE.options.to.size == 1 &&
[...SCOPE.options.to][0] instanceof Endpoint
)

if (
pointer_origin &&
Expand Down
28 changes: 27 additions & 1 deletion docs/manual/03 Pointers.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ Read more about transform functions in the chapter [Functional Programming](./09
## Using effects
With transform functions, value can be defined declaratively.
With transform functions, values can be defined declaratively.
Still, there are some scenarios where the actual pointer value change event must be handled with custom logic.
For this scenario, the `effect()` function can be used.
Expand Down Expand Up @@ -245,6 +245,32 @@ it is garbage colleted and the effect is removed.

Weak value bindings can be used with all *object* values, not just with pointers.

### Async effects

Effect callbacks cannot be `async` functions.
To handle async operations, you can always call an async function from inside the
effect callback:

```ts
const searchName = $$("");
const searchAge = $$(18);

// async function that searches for a user and shows the result somewhere
async function searchUser(name: string, age: number) {
const user = await query({type: "user", name, age});
showUser(user);
}

// effect that triggers the user search every time searchName or searchAge is changed
effect(() => searchUser(searchName.val, searchAge.val))
```

All dependency values of the effect must be accessed synchronously.
This means that the variables inside the async function don't trigger the effect, only the ones passed
into the `searchUser` call.



## Observing pointer changes

For more fine grained control, the `observe()` function can be used to handle pointer value updates.
Expand Down
76 changes: 76 additions & 0 deletions docs/manual/12 Classes.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,81 @@ obj.b = 15 // triggers observer
obj.sum // 26
```

## Constructors

The normal JavaScript class constructor gets called every time an instance of a sync class is created.
When an existing instance of a sync class is shared with another endpoint, the constructor is
called again locally on the endpoint, which is not intended but can't be prevented.

We recommend using DATEX-compatible constructors instead, which are only ever called once at the initial creation of a sync class instance.
The DATEX constructor method is named `construct` and must be decorated with `@constructor`:

```ts
@sync class MyObject {
@property a = 0
@property b = 0

// DATEX-compatible constructor
@constructor construct() {
console.log("constructed a new MyObject")
}
}

const obj = new MyObject() // "constructed a new MyObject" is logged
```

When the `obj` pointer is now accessed on a remote endpoint, the `construct` method
is not called again on the remote endpoint.

You can also access constructor arguments like in a normal constructor:
```ts
@sync class MyObject {
@constructor construct(a: number, b: string) {
console.log("a", a)
console.log("b", a)
}
}

const obj = new MyObject(42, 'text')
```

For correct typing, take a look at [this workaround](#workaround-to-get-correct-types).

## Creating instances without `new`

Class instances can also be created by calling the class as a function, passing
in an object with the initial property values:

```ts
@sync class MyObject {
@property a = 0
@property b = 0
}

const obj = MyObject({a: 1, b: 2})
```

Currently, this results in a TypeScript error, but it runs without problems.
You can use [this workaround](#workaround-to-get-correct-types) to get rid of the TypeScript errors.


## Workaround to get correct types

Currently, it is not possible to get the correct types for a sync class without some additional work.
You can add the following lines to a sync class to make the TypeScript compiler happy (this has no effect on the runtime behavior):
```ts
// sync class definition (private)
@sync class _MyObject {
@property a = 0
@property b = 0
}
// use these as public proxies for the actual class
export const MyObject = datexClass(_MyObject)
export type MyObject = datexClassType<typeof _MyObject>

const obj1: MyObject = new MyObject()
const obj2: MyObject = MyObject({a: 1, b: 2})
```

## Using the raw API
For more customization, you can directly use the [JavaScript interface API] which allows you to define custom DATEX mapping behaviours for specific JavaScript types.
62 changes: 40 additions & 22 deletions js_adapter/js_class_adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,13 +384,12 @@ export class Decorators {
let type: Type;

// get template type
if (typeof params[0] == "string") {
const typeString = params[0].replace(/^\</,'').replace(/\>$/,'')
type = Type.get(typeString.includes(":") ? typeString : "ext:"+typeString)
if (typeof params[0] == "string" || params[0] instanceof Type) {
type = normalizeType(params[0], false, "ext");
}
else if (params[0] instanceof Type) type = params[0];
else if (original_class[METADATA]?.[Decorators.FORCE_TYPE]?.constructor) type = original_class[METADATA]?.[Decorators.FORCE_TYPE]?.constructor
else type = Type.get("ext", original_class.name);
else type = Type.get("ext", original_class.name.replace(/^_/, '')); // remove leading _ from type name


// return new templated class
return createTemplateClass(original_class, type);
Expand All @@ -417,13 +416,11 @@ export class Decorators {
let type: Type;

// get template type
if (typeof params[0] == "string") {
const typeString = params[0].replace(/^\</,'').replace(/\>$/,'')
type = Type.get(typeString.includes(":") ? typeString : "ext:"+typeString)
if (typeof params[0] == "string" || params[0] instanceof Type) {
type = normalizeType(params[0], false, "ext");
}
else if (params[0] instanceof Type) type = params[0];
else if (original_class[METADATA]?.[Decorators.FORCE_TYPE]?.constructor) type = original_class[METADATA]?.[Decorators.FORCE_TYPE]?.constructor
else type = Type.get("ext", original_class.name);
else type = Type.get("ext", original_class.name.replace(/^_/, '')); // remove leading _ from type name

let callerFile:string|undefined;

Expand Down Expand Up @@ -488,15 +485,9 @@ export class Decorators {

/** @type(type:string|DatexType)/ (namespace:name)
* sync class with type */
static type(value:any, name:context_name, kind:context_kind, is_static:boolean, is_private:boolean, setMetadata:context_meta_setter, getMetadata:context_meta_getter, params:[(string|Type)?] = []) {

// handle decorator
if (typeof params[0] == "string") {
const [typeName, paramsString] = params[0].replace(/^\</,'').replace(/\>$/,'').match(/^(\w*)(?:\((.*)\))?$/)?.slice(1) ?? [];
const parsedParams = paramsString ? JSON.parse(`[${paramsString}]`) : undefined;
setMetadata(Decorators.FORCE_TYPE, Type.get(typeName, parsedParams))
}
else if (params[0] instanceof Type) setMetadata(Decorators.FORCE_TYPE, params[0])
static type(value:any, name:context_name, kind:context_kind, is_static:boolean, is_private:boolean, setMetadata:context_meta_setter, getMetadata:context_meta_getter, params:[(string|Type)] = []) {
const type = normalizeType(params[0]);
setMetadata(Decorators.FORCE_TYPE, type)
}

/** @from(type:string|DatexType): sync class from type */
Expand Down Expand Up @@ -574,7 +565,32 @@ export class Decorators {
}
}

globalThis.Decorators = Decorators;
/**
* Converts strings into Datex.Type and checks if type parameters are allowed
* @param type
* @param allowTypeParams
* @returns
*/
function normalizeType(type:Type|string, allowTypeParams = true, defaultNamespace = "std") {
if (typeof type == "string") {
// extract type name and parameters
const [typeName, paramsString] = type.replace(/^\</,'').replace(/\>$/,'').match(/^((?:\w+\:)?\w*)(?:\((.*)\))?$/)?.slice(1) ?? [];
if (paramsString && !allowTypeParams) throw new Error(`Type parameters not allowed (${type})`);

// TODO: only json-compatible params are allowed for now to avoid async
const parsedParams = paramsString ? JSON.parse(`[${paramsString}]`) : undefined;
return Type.get(typeName.includes(":") ? typeName : defaultNamespace+":"+typeName, parsedParams)
}
else if (type instanceof Type) {
if (!allowTypeParams && type.parameters?.length) throw new Error(`Type parameters not allowed (${type})`);
return type
}
else {
console.log(type)
throw new Error("Invalid type")
}
}


const initialized_static_scope_classes = new Map<Function,StaticScope>();

Expand Down Expand Up @@ -1182,7 +1198,9 @@ DatexFunction.setMethodMetaIndexSource(getMetaParamIndex)

// new version for implemented feature functions / attributes: call datex_advanced() on the class (ideally usa as a decorator, currently not supported by ts)

interface DatexClass<T extends Object = any> {
interface DatexClass<T extends (new (...args: unknown[]) => unknown) = (new (...args: unknown[]) => unknown), Construct = InstanceType<T>["construct"]> {

new(...args: Construct extends (...args: any) => any ? Parameters<Construct> : ConstructorParameters<T>): datexClassType<T>;

// special functions
on_result: (call: (data:any, meta:{station_id:number, station_bundle:number[]})=>any) => dc<T>;
Expand All @@ -1209,7 +1227,7 @@ type dc<T extends Record<string,any>&{new (...args:unknown[]):unknown}> = DatexC
* export type MyClass = datexClassType<typeof _MyClass>
* ```
*/
export function datexClass<T extends Record<string,any>&{new (...args:unknown[]):unknown}>(_class:T) {
export function datexClass<T extends Record<string,any>&{new (...args:any[]):any}>(_class:T) {
return <dc<ObjectRef<T>>> _class;
}

Expand Down
6 changes: 3 additions & 3 deletions js_adapter/legacy_decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ export function each(...args:any[]): any {
return handleDecoratorArgs(args, Decorators.each);
}

export function sync(type:string|Type):any
export function sync(type:string):any
export function sync(target: any, name?: string, method?:any):any
export function sync(...args:any[]): any {
return handleDecoratorArgs(args, Decorators.sync);
Expand All @@ -237,7 +237,7 @@ export function sync(...args:any[]): any {
/**
* @deprecated use \@sync
*/
export function template(type:string|Type):any
export function template(type:string):any
/**
* @deprecated use \@sync
*/
Expand Down Expand Up @@ -288,7 +288,7 @@ export function anonymous(...args:any[]) {

export function type(type:string|Type):any
export function type(...args:any[]): any {
return handleDecoratorArgs(args, Decorators.type);
return handleDecoratorArgs(args, Decorators.type, true);
}

export function assert(assertion:(val:any)=>boolean|string|undefined|null):any
Expand Down
12 changes: 11 additions & 1 deletion network/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface ComInterface {
endpoint?: Endpoint // connected directly to a single endpoint
endpoints?: Set<Endpoint> // multiple endpoints
is_bidirectional_hub?: boolean, // allow the same block to go in and out eg a -> this interface -> this runtime -> this interface again -> b
immediate?: boolean // can send immediately (eg. for local interfaces, workers)
isEqualSource?:(source: Partial<ComInterface>, to: Endpoint) => boolean
in: boolean // can receive data
out: boolean // can send data
Expand Down Expand Up @@ -162,6 +163,7 @@ export abstract class CommonInterface<Args extends unknown[] = []> implements Co
public in = true
public out = true
public global = true
public immediate = false

public get endpoint() {
return this._endpoint
Expand Down Expand Up @@ -197,7 +199,15 @@ export abstract class CommonInterface<Args extends unknown[] = []> implements Co
this.initial_arguments = args;

this.connected = await this.connect();
if (this.connected) this.updateEndpoint();
if (this.connected) {
this.updateEndpoint();
// immediately consider endpoint as online
if (this.endpoint && this.immediate) {
this.endpoint.setOnline(true)
// don't trigger subscription cleanup once first HELLO message is received
this.endpoint.ignoreHello = true;
}
}
return this.connected;
}

Expand Down
28 changes: 18 additions & 10 deletions runtime/endpoint_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,21 +94,29 @@ class EndpointConfig implements EndpointConfigData {
config = <EndpointConfigData> await Runtime.executeDatexLocally(serialized, undefined, undefined, globalThis.location?.href ? new URL(globalThis.location.href) : undefined)
}
// try to get from .dx url
else {
if (!path) path = new URL('/'+this.DX_FILE_NAME, globalThis.location.href)
try {
config = await datex.get(path);
if (!path) path = new URL('/'+this.DX_FILE_NAME, globalThis.location.href)
try {
const configUpdate = await datex.get<EndpointConfigData>(path);
if (!config) {
config = configUpdate;
logger.info("loaded endpoint config from " + path);
}
catch (e) {
// ignore if no .dx file found
if (!(await fetch(path)).ok) {}
else {
logger.error `Could not read config file ${path}: ${e.toString()}`;
throw "invalid config file"
else {
for (const [key, value] of DatexObject.entries(configUpdate as Record<string|symbol,unknown>)) {
DatexObject.set(config as Record<string|symbol,unknown>, key as string, value);
}
logger.debug("updated endpoint config from " + path);
}
}
catch (e) {
// ignore if no .dx file found
if (!(await fetch(path)).ok) {}
else {
logger.error `Could not read config file ${path}: ${e.toString()}`;
throw "invalid config file"
}
}

}
else {
logger.debug("Cannot load endpoint config file for client type '" + client_type + "'")
Expand Down
Loading

0 comments on commit be20156

Please sign in to comment.