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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ obj
*.user
*.nupkg
package-lock.json
last-failed-test-dump.txt
last-failed-test-dump.txt
22 changes: 15 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
# Requirements

- Keep the code lean and efficient, including the use of `unsafe` when it is justified.
- Use the latest available .NET and C# features when they improve the code and fit the existing style.
- Avoid defensive programming and compatibility overhead. Target only the modern WASM runtime, current JS specs and current browser capabilities.
- Follow the existing code style, architecture, project structure, naming and formatting strictly.
- Follow the existing code style, architecture, naming and formatting strictly.
- Use the latest C# features when they fit the existing style.
- Avoid defensive programming and compatibility overhead.
- If clarification is required, use the question tool instead of guessing.

IMPORTANT: NEVER RUN ANY BUILD/PUBLISH COMMANDS IN PARALLEL.
# Export-Import Model

We have "export" and "import" concepts used throughout the codebase. The model is always C#-centric and means the same thing on both the C# and JavaScript sides:

- Export: something in C# being exported to JavaScript
- Import: something in JavaScript being imported to C#

For example, an exported method means a C# method exposed to JavaScript, and we refer to it as exported in both the C# and JavaScript code. An imported method means the opposite: a JavaScript function bound to a partial C# method, referred to as imported in both C# and JS code.

Make sure to follow this convention strictly.

# Packaging Bootsharp

Follow these steps exactly and sequentially whenever the Bootsharp package consumed by other projects must be actualized, or when running the JS end-to-end tests after updating the package's C# or JS code.
Follow these steps exactly and sequentially whenever the Bootsharp package consumed by other projects must be actualized, or when running the JS end-to-end tests after modifying the package's C# or JS code.

1. Build the JS package with `npm run build` under `src/js`.
2. Bump the Bootsharp library alpha version in `src/cs/Directory.Build.props`
Expand All @@ -30,7 +38,7 @@ We have a strict 100% coverage policy for both the C# and JS codebases.

- Tests must be meaningful and cover real behavior.
- Do not add fake tests just to satisfy the numbers.
- No unreachable code is allowed, except in rare cases where testing is not practical. In those cases, ask how to proceed.
- No unreachable code is allowed, except in rare cases where testing is not practical.
- Treat branch coverage as part of the requirement, not just line coverage.

To check C# coverage, use `reportgenerator` on merged coverlet output. Example workflow reference: `src/cs/.scripts/cover.sh`. Do not run that script verbatim in automation; it is intended for interactive usage.
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
142 changes: 79 additions & 63 deletions docs/guide/declarations.md
Original file line number Diff line number Diff line change
@@ -1,87 +1,91 @@
# Type Declarations

Bootsharp will automatically generate [type declarations](https://www.typescriptlang.org/docs/handbook/2/type-declarations) for interop APIs when building the solution. The files are emitted under "types" directory of the compiled module package.
Bootsharp will automatically generate [type declarations](https://www.typescriptlang.org/docs/handbook/2/type-declarations) for interop APIs when building the solution. One `.g.d.mts` file is emitted per C# namespace, colocated with the matching `.g.mjs` binding under the `generated/` directory of the compiled module package.

## Function Declarations

For the interop methods, function declarations are emitted.

Exported methods will have associated function assigned under the declaring type space:
For interop methods, function declarations are emitted under the class's TS namespace wrapper inside the C# namespace's module:

```csharp
public class Foo
namespace Foo;

public class Bar
{
[Export]
public static void Bar() { }
public static void Baz() { }
}
```

— will make following emitted in the declaration file:
— will make the following emitted in `generated/foo.g.d.mts`:

```ts
export namespace Foo {
export function bar(): void;
export namespace Bar {
export function baz(): void;
}
```

— which allows consuming the API in JavaScript as follows:
— which allows consuming the API in JavaScript as:

```ts
import { Foo } from "bootsharp";
import { Bar } from "bootsharp/foo";

Foo.bar();
Bar.baz();
```

Imported methods will be emitted as properties, which have to be assigned before booting the runtime:

::: code-group

```csharp [Foo.cs]
public partial class Foo
```csharp [Bar.cs]
namespace Foo;

public partial class Bar
{
[Import]
public static partial void Bar();
public static partial void Baz();
}
```

```ts [bindings.d.ts]
export namespace Foo {
export let bar: () => void;
```ts [foo.g.d.mts]
export namespace Bar {
export let baz: () => void;
}
```

```ts [main.ts]
import { Foo } from "bootsharp";
import { Bar } from "bootsharp/foo";

Foo.bar = () => {};
Bar.baz = () => {};
```

:::

## Property Declarations

Exported properties are emitted as variables under the declaring type space:
Exported properties are emitted as variables under the declaring class's TS namespace:

::: code-group

```csharp [Foo.cs]
public class Foo
```csharp [Bar.cs]
namespace Foo;

public class Bar
{
[Export]
public static string Bar { get; set; } = "";
public static string Baz { get; set; } = "";
}
```

```ts [bindings.d.ts]
export namespace Foo {
export let bar: string;
```ts [foo.g.d.mts]
export namespace Bar {
export let baz: string;
}
```

```ts [main.ts]
import { Foo } from "bootsharp";
import { Bar } from "bootsharp/foo";

Foo.bar = "updated";
Bar.baz = "updated";
```

:::
Expand All @@ -90,25 +94,27 @@ Imported properties are emitted as accessor pairs, which have to be assigned bef

::: code-group

```csharp [Foo.cs]
public static partial class Foo
```csharp [Bar.cs]
namespace Foo;

public static partial class Bar
{
[Import]
public static partial string Bar { get; set; }
public static partial string Baz { get; set; }
}
```

```ts [bindings.d.ts]
export namespace Foo {
export let bar: { get: () => string; set: (value: string) => void };
```ts [foo.g.d.mts]
export namespace Bar {
export let baz: { get: () => string; set: (value: string) => void };
}
```

```ts [main.ts]
import { Foo } from "bootsharp";
import { Bar } from "bootsharp/foo";

let bar = "";
Foo.bar = { get: () => bar, set: value => bar = value };
let baz = "";
Bar.baz = { get: () => baz, set: value => baz = value };
```

:::
Expand All @@ -119,24 +125,26 @@ Exported events are emitted as `EventSubscriber` objects:

::: code-group

```csharp [Foo.cs]
public class Foo
```csharp [Bar.cs]
namespace Foo;

public class Bar
{
[Export]
public static event Action<string>? OnBar;
public static event Action<string>? OnBaz;
}
```

```ts [bindings.d.ts]
export namespace Foo {
export const onBar: EventSubscriber<[payload: string]>;
```ts [foo.g.d.mts]
export namespace Bar {
export const onBaz: EventSubscriber<[payload: string]>;
}
```

```ts [main.ts]
import { Foo } from "bootsharp";
import { Bar } from "bootsharp/foo";

Foo.onBar.subscribe(payload => {});
Bar.onBaz.subscribe(payload => {});
```

:::
Expand All @@ -145,24 +153,26 @@ Imported events are emitted as `EventBroadcaster` objects:

::: code-group

```csharp [Foo.cs]
public static partial class Foo
```csharp [Bar.cs]
namespace Foo;

public static partial class Bar
{
[Import]
public static event Action<string>? OnBar;
public static event Action<string>? OnBaz;
}
```

```ts [bindings.d.ts]
export namespace Foo {
export const onBar: EventBroadcaster<[payload: string]>;
```ts [foo.g.d.mts]
export namespace Bar {
export const onBaz: EventBroadcaster<[payload: string]>;
}
```

```ts [main.ts]
import { Foo } from "bootsharp";
import { Bar } from "bootsharp/foo";

Foo.onBar.broadcast("updated");
Bar.onBaz.broadcast("updated");
```

:::
Expand All @@ -173,7 +183,9 @@ When an inspected assembly has XML documentation generated, Bootsharp mirrors th

::: code-group

```csharp [Foo.cs]
```csharp [MathApi.cs]
namespace Foo;

/// <summary>Math API.</summary>
public class MathApi
{
Expand All @@ -186,7 +198,7 @@ public class MathApi
}
```

```ts [bindings.d.ts]
```ts [foo.g.d.mts]
/**
* Math API.
*/
Expand Down Expand Up @@ -216,28 +228,32 @@ This is intentional and optimized for TypeScript ergonomics. Refer to the dedica

## Type Crawling

Bootsharp will crawl types from the interop signatures and mirror them in the emitted declarations. For example, if you have a custom record with property of another custom record implementing a custom interface, both records and the interface will be emitted:
Bootsharp will crawl types from the interop signatures and mirror them as top-level exports of the same C# namespace's declaration module. For example, if you have a custom record with a property of another custom record implementing a custom interface, both records and the interface will be emitted:

::: code-group

```csharp [Foo.cs]
namespace Space;

public interface IFoo { };
public record Foo : IFoo;
public record Bar (Foo foo);

public partial class Foo
public partial class Holder
{
[Import]
public static partial Bar GetBar();
}
```

```ts [bindings.d.ts]
```ts [space.g.d.mts]
export interface IFoo {}
export interface Foo implements IFoo {}
export interface Bar {foo: Foo;}
export type Foo = IFoo & Readonly<{}>;
export type Bar = Readonly<{
foo: Foo;
}>;

export namespace Foo {
export namespace Holder {
export function getBar(): Bar;
}
```
Expand All @@ -246,4 +262,4 @@ export namespace Foo {

## Configuring Type Mappings

You can override which type declaration are generated for associated C# types via `Type` patterns of [emit preferences](/guide/preferences).
You can override which type declaration is generated for associated C# types via `Type` patterns of [emit preferences](/guide/preferences).
Loading
Loading