Skip to content

C# submodules - #5974

Merged
lisandroct merged 52 commits into
masterfrom
lisandro/csharp-submodules
Oct 13, 2026
Merged

lisandroct merged 52 commits into
masterfrom
lisandro/csharp-submodules

Conversation

@lisandroct

@lisandroct lisandroct commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Description of Changes

Implements C# submodules (namespacing), following the proposal and reusing the existing V10 submodule representation.

  • Automatically discovers and registers referenced C# module assemblies.
  • Adds assembly-level [Namespace(typeof(AuthLib.Marker), Accessor = "MyAuth", Name = "auth_data" )] declarations. Accessor controls C# access; optional Name specifies the canonical database namespace. When omitted, the host derives it using the root module’s case-conversion policy. Dependencies without a namespace declaration register in public.
  • Allows the same module assembly to be published independently or used as a dependency, without a separate library build role.
  • Shares .NET 10 contexts across assemblies. Root code accesses ctx.Db.MyAuth.User; library helpers retain ctx.Db.User and use the caller’s context and transaction.
  • Supports namespace-aware tables, indexes, views, query builders, and scheduled reducers/procedures.
  • Generates nested C# client table, reducer, procedure, and subscription accessors using one connection and cache.
  • Caches resolved lookup names, SQL names, and index handles to avoid repeated resolution and unnecessary allocations.
  • Adds diagnostics, documentation, CI coverage, and focused integration tests.

API and ABI breaking changes

No changes to host, it uses the existing RawSubmoduleV10 for accessor namespaces and the existing ExplicitNames namespace mappings for canonical names.

On .NET 10, contexts move from generated module types into Runtime, changing their assembly identity. Existing standalone .NET 8 modules retain generated contexts.

Rollback safety impact

n/a

Expected complexity level and risk

4/5. This PR touches a lot of systems: source generation, runtime registration, NativeAOT publication, schema composition, function dispatch, client generation, subscriptions, ... possibly more.

The main risks are incorrect assembly ownership or dispatch ordering, cross-namespace lookup mistakes, NativeAOT export/trimming behavior, and regressions in existing flat modules.

Testing

  • .NET 8 and .NET 10 generator/Verify suites; regenerated snapshots.
  • Rust codegen tests, including C# bindings generated from a TypeScript submodule.
  • NativeAOT publication, root export selection, and cross-library helper/HTTP integration.
  • Namespace client integration: subscriptions, callbacks, indexes, views, events, private tables, one-off queries, and unsubscribe cleanup.
  • Namespaced scheduled reducers/procedures and cross-namespace transaction commit/rollback.
  • Root-defined RLS targeting namespaced tables and environment-security integration.
  • Distinct accessor/canonical names (MyAuth → auth_data) and default namespace case conversion (MyAuth → my_auth).
  • Full C# SDK unit-test suite.

@lisandroct
lisandroct marked this pull request as draft September 23, 2026 17:07
@lisandroct
lisandroct force-pushed the lisandro/csharp-submodules branch 2 times, most recently from 7ee3c50 to ab24ef3 Compare September 28, 2026 14:05
@lisandroct
lisandroct marked this pull request as ready for review September 28, 2026 15:18
@lisandroct
lisandroct force-pushed the lisandro/csharp-submodules branch 2 times, most recently from 03252b0 to 3d1b967 Compare October 1, 2026 14:32

@JasonAtClockwork JasonAtClockwork left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything works as intended but I did find some differences between how the TypeScript submodules work compared against the C#. I'm not certain if any of these are real problems so we may want a discussion around them:

  1. HTTP routes from submodules are ignored in TypeScript but are bound to the root in C# (which can cause collisions)
  2. TypeScript supports nested submodules, and generating a C# client against a TypeScript module with nested submodules causes spacetime generate to panic
  3. TypeScript submodules are declared, not discovered. C# discovers every referenced module and merges anything not mounted into the root (master currently just ignored them), which can cause collisions with the root's reducers, Init, or types and allows access to root env variables.

Super low priority: Codegen.Tests fails on Windows however is fine on Linux

@lisandroct

Copy link
Copy Markdown
Contributor Author
1. HTTP routes from submodules are ignored in TypeScript but are bound to the root in C# (which can cause collisions)

The proposal doesn't say anything about HTTP handlers. I decided to not ignore them, but it might be better to do so.

2. TypeScript supports nested submodules, and generating a C# client against a TypeScript module with nested submodules causes `spacetime generate` to panic

The proposal only describes submodules being defined in root and I was under the impression that the plan was to not support nested submodules, so I just didn't add support to them. But now I'm not sure. Maybe we do want them?

3. TypeScript submodules are declared, not discovered. C# discovers every referenced module and merges anything not mounted into the root (master currently just ignored them), which can cause collisions with the root's reducers, Init, or types and allows access to root env variables.

The proposal states: "A library added as a dependency and otherwise ignored still has its tables register in public, exactly as today; what changes is that the consumer can redirect them to a named namespace via a mount declaration". So I understand the behavior on this branch is the desired one. All dependencies are registered in public unless root defines a namespace for them.

@lisandroct
lisandroct force-pushed the lisandro/csharp-submodules branch from 3d1b967 to 6278735 Compare October 6, 2026 13:32

@JasonAtClockwork JasonAtClockwork left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@jdetter jdetter left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'm only required for the CI change and that looks fine to me. I didn't review any of the other code.

@lisandroct
lisandroct added this pull request to the merge queue Oct 8, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 8, 2026
@lisandroct
lisandroct force-pushed the lisandro/csharp-submodules branch from ad38fb3 to 7f91b8f Compare October 8, 2026 18:39
@lisandroct
lisandroct enabled auto-merge October 8, 2026 18:43
@lisandroct
lisandroct force-pushed the lisandro/csharp-submodules branch 2 times, most recently from 45a864b to 18e2b47 Compare October 9, 2026 19:54
@lisandroct
lisandroct force-pushed the lisandro/csharp-submodules branch from 18e2b47 to 673ef3b Compare October 13, 2026 18:05
@lisandroct
lisandroct added this pull request to the merge queue Oct 13, 2026
Merged via the queue into master with commit d3b49e9 Oct 13, 2026
74 checks passed
@lisandroct lisandroct mentioned this pull request Oct 14, 2026
3 tasks
github-merge-queue Bot pushed a commit that referenced this pull request Oct 21, 2026
# Description of Changes

<!-- Please describe your change, mention any related tickets, and so on
here. -->

This PR adds support for nested C# submodules. Multiple independent
instances of the same module assembly can be mounted independently of
each other (for example: submodule `A` can mount submodule `C` and
submodule `B` can also mount it's own submodule `C`).
- Route tables, indexes, reducers, procedures, and views to the correct
module instance.
- Add `ctx.As` for passing an instance-selected context to functions
that receive context.
- Add instance-aware `ctx.From` query builders.
- Add context-aware immediate scheduling overloads for reducers and
procedures.
- Add nested namespace paths in generated C# client queries (for example
`ctx.Db.SubmoduleA.SubmoduleC.{...}`).

# API and ABI breaking changes

<!-- If this is an API or ABI breaking change, please apply the
corresponding GitHub label. -->

No breaking changes.
This PR extends what we added in
#5974 but everything
stays compatible. Before, if a submodule mounted a submodule, that would
throw an error; now it's supported.

# Rollback safety impact

<!--
If this PR is safe to roll back after deployment and has no impact on
the rollback safety of any prior PRs, write `n/a` below.

Otherwise, list every prerequisite PR that must be included in a release
before this PR can merge. This PR must not be deployed until each
prerequisite has been deployed and sufficiently observed or tested in
the live deployment that we are confident it will not be rolled back.
Supported forms include #123, SpacetimeDB#123,
#123, and GitHub PR URLs.

Consider whether this PR:
- newly writes to a previously introduced but unused ControlDB table or
reducer, system table, or on-disk data format; or
- clears, deletes, incompatibly changes, or renders unsupported a
previously available ControlDB table or reducer, system table, or
on-disk data format.
-->

n/a

# Expected complexity level and risk

<!--
How complicated do you think these changes are? Grade on a scale from 1
to 5,
where 1 is a trivial change, and 5 is a deep-reaching and complex
change.

This complexity rating applies not only to the complexity apparent in
the diff,
but also to its interactions with existing and future code.

If you answered more than a 2, explain what is complex about the PR,
and what other components it interacts with in potentially concerning
ways. -->

4/5. This PR touches a lot of things: it changes module composition,
generated accessors, function dispatch, runtime context handling, SQL
name generation...

# Testing

<!-- Describe any testing you've done, and any testing you'd like your
reviewers to do,
so that you're confident that all the changes work as expected! -->

I added generator diagnostics and snapshot coverage, a nested-module
host integration fixture, and client regressions targeting .NET 8 and
.NET 10. Coverage includes independent tables and caches, deep namespace
paths, dispatch, queries and joins, context selection, transaction
commit/rollback, and immediate scheduling.

 - [ ] Run C# generator tests on .NET 8 and .NET 10.
 - [ ] Run the namespace_csharp standalone integration tests.
- [ ] Run the C# client regression harness against the updated local SDK
packages.

---------

Signed-off-by: Alessandro Asoni <alessandro@clockworklabs.io>
Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com>
Co-authored-by: Alessandro Asoni <alessandro@clockworklabs.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants