Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions docs/guide/interop-instances.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,21 @@
When an interface is supplied as argument or return type of an interop method, instead of serializing it as value, Bootsharp will instead generate an instance binding, eg:

```csharp
public interface IExported { string GetFromCSharp (); }
public interface IImported { string GetFromJavaScript (); }
public interface IExported
{
string Value { get; set; }
string GetFromCSharp ();
}

public interface IImported
{
string Value { get; set; }
string GetFromJavaScript ();
}

public class Exported : IExported
{
public string Value { get; set; } = "cs";
public string GetFromCSharp () => "cs";
}

Expand All @@ -18,20 +28,25 @@ public static partial class Factory
}

var imported = Factory.GetImported();
imported.GetFromJavaScript(); //returns "js"
imported.GetFromJavaScript(); // returns "js"
imported.value = "updated"; // invokes the JS setter
_ = imported.value; // invokes the JS getter
```

```ts
import { Factory, IImported } from "bootsharp";

class Imported implements IImported {
value = "js";
getFromJavaScript() { return "js"; }
}

Factory.getImported = () => new Imported();

const exported = Factory.getExported();
exported.getFromCSharp(); // returns "cs"
exported.value = "updated"; // invokes the C# setter
_ = exported.value; // invokes the C# getter
```

Interop instances are subject to the following limitations:
Expand Down
25 changes: 5 additions & 20 deletions docs/guide/interop-interfaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ For example, say we have a JavaScript UI (frontend) that needs to be notified wh
```csharp
interface IFrontend
{
bool IsMuted { get; set; }
void NotifyDataChanged (Data data);
bool IsMuted ();
}
```

Expand All @@ -24,8 +24,8 @@ Bootsharp will automatically implement the interface in C#, wiring it to JavaScr

```ts
export namespace Frontend {
export let isMuted: boolean;
export const onDataChanged: Event<[Data]>;
export let isMuted: () => boolean;
}
```

Expand All @@ -34,6 +34,7 @@ Now, say we want to provide an API for the frontend to request a mutation of the
```csharp
interface IBackend
{
Data? Current { get; set; }
void AddData (Data data);
}
```
Expand All @@ -46,27 +47,11 @@ Export the interface to JavaScript:
])]
```

This will generate the following implementation:

```csharp
public class JSBackend
{
private static IBackend handler = null!;

public JSBackend (IBackend handler)
{
JSBackend.handler = handler;
}

[JSInvokable]
public static void AddData (Data data) => handler.AddData(data);
}
```

— which will produce the following spec to be consumed on the JavaScript side:
This will produce the following spec to be consumed on the JavaScript side:

```ts
export namespace Backend {
export let current: Data | null;
export function addData(data: Data): void;
}
```
Expand Down
22 changes: 22 additions & 0 deletions docs/guide/serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,25 @@ import { Program } from "bootsharp";
const map = Program.map(["foo", "bar"], [0, 7]);
console.log(map.get("bar")); // 7
```

## Computed Properties

Computed C# properties are evaluated and written to the JS objects on serialization. For example:

```csharp
public record Order
{
public required string Id { get; init; }
public required int Revision { get; init; }
public string Version => $"{Id}:{Revision}";
}
```

When an `Order` crosses the C# -> JavaScript boundary, Bootsharp reads `Version` on the C# side and writes the result to the JavaScript object as a regular persisted value:

```ts
const order = Orders.getCurrent();
console.log(order.version); // "A:7"
```

The value is computed only while the C# object is being serialized. After that, it's just a normal JavaScript property on the received object.
150 changes: 75 additions & 75 deletions src/cs/Bootsharp.Publish.Test/Emit/InterfacesTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public record Record;

public interface IExported
{
Record? Record { get; set; }

void Inv (string? a);
Task InvAsync ();
Record? InvRecord ();
Expand All @@ -25,6 +27,18 @@ public interface IExported
Execute();
Contains(
"""
namespace Bootsharp.Generated
{
internal static class InterfaceRegistrations
{
[System.Runtime.CompilerServices.ModuleInitializer]
internal static void RegisterInterfaces ()
{
Interfaces.Register(typeof(Bootsharp.Generated.Exports.JSExported), new ExportInterface(typeof(global::IExported), handler => new Bootsharp.Generated.Exports.JSExported((global::IExported)handler)));
}
}
}

namespace Bootsharp.Generated.Exports
{
public class JSExported
Expand All @@ -36,6 +50,8 @@ public JSExported (global::IExported handler)
JSExported.handler = handler;
}

[JSInvokable] public static global::Record? GetPropertyRecord () => handler.Record;
[JSInvokable] public static void SetPropertyRecord (global::Record? value) => handler.Record = value;
[JSInvokable] public static void Inv (global::System.String? a) => handler.Inv(a);
[JSInvokable] public static global::System.Threading.Tasks.Task InvAsync () => handler.InvAsync();
[JSInvokable] public static global::Record? InvRecord () => handler.InvRecord();
Expand All @@ -44,20 +60,6 @@ public JSExported (global::IExported handler)
}
}
""");
Contains(
"""
namespace Bootsharp.Generated
{
internal static class InterfaceRegistrations
{
[System.Runtime.CompilerServices.ModuleInitializer]
internal static void RegisterInterfaces ()
{
Interfaces.Register(typeof(Bootsharp.Generated.Exports.JSExported), new ExportInterface(typeof(global::IExported), handler => new Bootsharp.Generated.Exports.JSExported((global::IExported)handler)));
}
}
}
""");
}

[Fact]
Expand All @@ -71,6 +73,8 @@ public record Record;

public interface IImported
{
Record? Record { get; set; }

void Inv (string? a);
Task InvAsync ();
Record? InvRecord ();
Expand All @@ -79,26 +83,6 @@ public interface IImported
}
"""));
Execute();
Contains(
"""
namespace Bootsharp.Generated.Imports
{
public class JSImported : global::IImported
{
[JSFunction] public static void Inv (global::System.String? a) => global::Bootsharp.Generated.Interop.Proxy_Bootsharp_Generated_Imports_JSImported_Inv(a);
[JSFunction] public static global::System.Threading.Tasks.Task InvAsync () => global::Bootsharp.Generated.Interop.Proxy_Bootsharp_Generated_Imports_JSImported_InvAsync();
[JSFunction] public static global::Record? InvRecord () => global::Bootsharp.Generated.Interop.Proxy_Bootsharp_Generated_Imports_JSImported_InvRecord();
[JSFunction] public static global::System.Threading.Tasks.Task<global::System.String> InvAsyncResult () => global::Bootsharp.Generated.Interop.Proxy_Bootsharp_Generated_Imports_JSImported_InvAsyncResult();
[JSFunction] public static global::System.String[] InvArray (global::System.Int32[] a) => global::Bootsharp.Generated.Interop.Proxy_Bootsharp_Generated_Imports_JSImported_InvArray(a);

void global::IImported.Inv (global::System.String? a) => Inv(a);
global::System.Threading.Tasks.Task global::IImported.InvAsync () => InvAsync();
global::Record? global::IImported.InvRecord () => InvRecord();
global::System.Threading.Tasks.Task<global::System.String> global::IImported.InvAsyncResult () => InvAsyncResult();
global::System.String[] global::IImported.InvArray (global::System.Int32[] a) => InvArray(a);
}
}
""");
Contains(
"""
namespace Bootsharp.Generated
Expand All @@ -112,6 +96,23 @@ internal static void RegisterInterfaces ()
}
}
}

namespace Bootsharp.Generated.Imports
{
public class JSImported : global::IImported
{
global::Record? global::IImported.Record
{
get => global::Bootsharp.Generated.Interop.Proxy_Bootsharp_Generated_Imports_JSImported_GetPropertyRecord();
set => global::Bootsharp.Generated.Interop.Proxy_Bootsharp_Generated_Imports_JSImported_SetPropertyRecord(value);
}
void global::IImported.Inv (global::System.String? a) => global::Bootsharp.Generated.Interop.Proxy_Bootsharp_Generated_Imports_JSImported_Inv(a);
global::System.Threading.Tasks.Task global::IImported.InvAsync () => global::Bootsharp.Generated.Interop.Proxy_Bootsharp_Generated_Imports_JSImported_InvAsync();
global::Record? global::IImported.InvRecord () => global::Bootsharp.Generated.Interop.Proxy_Bootsharp_Generated_Imports_JSImported_InvRecord();
global::System.Threading.Tasks.Task<global::System.String> global::IImported.InvAsyncResult () => global::Bootsharp.Generated.Interop.Proxy_Bootsharp_Generated_Imports_JSImported_InvAsyncResult();
global::System.String[] global::IImported.InvArray (global::System.Int32[] a) => global::Bootsharp.Generated.Interop.Proxy_Bootsharp_Generated_Imports_JSImported_InvArray(a);
}
}
""");
}

Expand All @@ -120,8 +121,15 @@ public void GeneratesImplementationForInstancedImportInterface ()
{
AddAssembly(With(
"""
public record Record;
public interface IExported { void Inv (string arg); }
public interface IImported { void Fun (string arg); void NotifyEvt(string arg); }
public interface IImported
{
Record? Record { get; set; }

void Fun (string arg);
void NotifyEvt (string arg);
}

public class Class
{
Expand All @@ -138,11 +146,13 @@ public class JSImported(global::System.Int32 _id) : global::IImported
{
~JSImported() => global::Bootsharp.Generated.Interop.DisposeImportedInstance(_id);

[JSFunction] public static void Fun (global::System.Int32 _id, global::System.String arg) => global::Bootsharp.Generated.Interop.Proxy_Bootsharp_Generated_Imports_JSImported_Fun(_id, arg);
[JSEvent] public static void OnEvt (global::System.Int32 _id, global::System.String arg) => global::Bootsharp.Generated.Interop.Proxy_Bootsharp_Generated_Imports_JSImported_OnEvt(_id, arg);

void global::IImported.Fun (global::System.String arg) => Fun(_id, arg);
void global::IImported.NotifyEvt (global::System.String arg) => OnEvt(_id, arg);
global::Record? global::IImported.Record
{
get => global::Bootsharp.Generated.Interop.Proxy_Bootsharp_Generated_Imports_JSImported_GetPropertyRecord(_id);
set => global::Bootsharp.Generated.Interop.Proxy_Bootsharp_Generated_Imports_JSImported_SetPropertyRecord(_id, value);
}
void global::IImported.Fun (global::System.String arg) => global::Bootsharp.Generated.Interop.Proxy_Bootsharp_Generated_Imports_JSImported_Fun(_id, arg);
void global::IImported.NotifyEvt (global::System.String arg) => global::Bootsharp.Generated.Interop.Proxy_Bootsharp_Generated_Imports_JSImported_OnEvt(_id, arg);
}
}
""");
Expand All @@ -167,6 +177,19 @@ public interface IImported { void Fun (Record a); }
Execute();
Contains(
"""
namespace Bootsharp.Generated
{
internal static class InterfaceRegistrations
{
[System.Runtime.CompilerServices.ModuleInitializer]
internal static void RegisterInterfaces ()
{
Interfaces.Register(typeof(Bootsharp.Generated.Exports.Space.JSExported), new ExportInterface(typeof(global::Space.IExported), handler => new Bootsharp.Generated.Exports.Space.JSExported((global::Space.IExported)handler)));
Interfaces.Register(typeof(global::Space.IImported), new ImportInterface(new Bootsharp.Generated.Imports.Space.JSImported()));
}
}
}

namespace Bootsharp.Generated.Exports.Space
{
public class JSExported
Expand All @@ -181,28 +204,12 @@ public JSExported (global::Space.IExported handler)
[JSInvokable] public static void Inv (global::Space.Record a) => handler.Inv(a);
}
}

namespace Bootsharp.Generated.Imports.Space
{
public class JSImported : global::Space.IImported
{
[JSFunction] public static void Fun (global::Space.Record a) => global::Bootsharp.Generated.Interop.Proxy_Bootsharp_Generated_Imports_Space_JSImported_Fun(a);

void global::Space.IImported.Fun (global::Space.Record a) => Fun(a);
}
}
""");
Contains(
"""
namespace Bootsharp.Generated
{
internal static class InterfaceRegistrations
{
[System.Runtime.CompilerServices.ModuleInitializer]
internal static void RegisterInterfaces ()
{
Interfaces.Register(typeof(Bootsharp.Generated.Exports.Space.JSExported), new ExportInterface(typeof(global::Space.IExported), handler => new Bootsharp.Generated.Exports.Space.JSExported((global::Space.IExported)handler)));
Interfaces.Register(typeof(global::Space.IImported), new ImportInterface(new Bootsharp.Generated.Imports.Space.JSImported()));
}
void global::Space.IImported.Fun (global::Space.Record a) => global::Bootsharp.Generated.Interop.Proxy_Bootsharp_Generated_Imports_Space_JSImported_Fun(a);
}
}
""");
Expand All @@ -224,9 +231,7 @@ namespace Bootsharp.Generated.Imports
{
public class JSImported : global::IImported
{
[JSEvent] public static void OnFoo () => global::Bootsharp.Generated.Interop.Proxy_Bootsharp_Generated_Imports_JSImported_OnFoo();

void global::IImported.NotifyFoo () => OnFoo();
void global::IImported.NotifyFoo () => global::Bootsharp.Generated.Interop.Proxy_Bootsharp_Generated_Imports_JSImported_OnFoo();
}
}
""");
Expand All @@ -247,20 +252,6 @@ public interface IImported
}
"""));
Execute();
Contains(
"""
namespace Bootsharp.Generated.Imports
{
public class JSImported : global::IImported
{
[JSFunction] public static void NotifyFoo () => global::Bootsharp.Generated.Interop.Proxy_Bootsharp_Generated_Imports_JSImported_NotifyFoo();
[JSEvent] public static void OnBar () => global::Bootsharp.Generated.Interop.Proxy_Bootsharp_Generated_Imports_JSImported_OnBar();

void global::IImported.NotifyFoo () => NotifyFoo();
void global::IImported.BroadcastBar () => OnBar();
}
}
""");
Contains(
"""
namespace Bootsharp.Generated
Expand All @@ -274,6 +265,15 @@ internal static void RegisterInterfaces ()
}
}
}

namespace Bootsharp.Generated.Imports
{
public class JSImported : global::IImported
{
void global::IImported.NotifyFoo () => global::Bootsharp.Generated.Interop.Proxy_Bootsharp_Generated_Imports_JSImported_NotifyFoo();
void global::IImported.BroadcastBar () => global::Bootsharp.Generated.Interop.Proxy_Bootsharp_Generated_Imports_JSImported_OnBar();
}
}
""");
}

Expand Down
Loading
Loading