Skip to content

Auto-generate TypeScript route definitions from endpoint metadata#87

Merged
antosubash merged 4 commits intomainfrom
claude/type-safe-routes-JELuV
Apr 6, 2026
Merged

Auto-generate TypeScript route definitions from endpoint metadata#87
antosubash merged 4 commits intomainfrom
claude/type-safe-routes-JELuV

Conversation

@antosubash
Copy link
Copy Markdown
Owner

Summary

This PR implements automatic generation of TypeScript route definitions from C# endpoint metadata, eliminating the need to manually maintain route constants across the codebase. Routes are now centrally defined in module Constants files and automatically exported to TypeScript.

Key Changes

  • New Route Generation System

    • Added RoutesEmitter.cs to generate C# ModuleRoutes class with strongly-typed route constants
    • Added TypeScriptRoutesEmitter.cs to generate TypeScript routes object with camelCase module names and typed route functions
    • Added extract-routes.mjs tool to extract TypeScript routes from generated C# files into the client package
  • Module Constants Consolidation

    • Created *Constants.cs files in .Contracts projects for all modules (Users, Orders, Email, FileStorage, PageBuilder, Settings, Tenants, BackgroundJobs, AuditLogs, Products, RateLimiting, Marketplace, Admin, Dashboard, FeatureFlags, OpenIddict)
    • Each Constants file defines ModuleName, RoutePrefix, ViewPrefix, and nested Routes class with endpoint route constants
    • Removed duplicate Constants files from main module projects, now referencing Contracts versions
  • Endpoint Route Metadata

    • Updated SymbolDiscovery.cs to capture RouteTemplate from endpoint definitions
    • Updated DiscoveryData.cs to include route template information in EndpointInfoRecord
    • Added diagnostic SM0054 to warn when endpoints are missing Route const fields
  • TypeScript Integration

    • Generated routes.ts in @simplemodule/client package with fully typed route accessors
    • Routes organized by module with api and views sub-objects
    • Route functions accept parameters (id, name, etc.) and return properly formatted paths
    • Updated 50+ React components and endpoints to import and use the generated routes
    • Added routes.ts re-export in template project for convenience
  • Build Integration

    • Updated SimpleModule.Hosting.targets to run route extraction during build
    • Updated ModuleDiscovererGenerator.cs to invoke new emitters
    • Updated package.json build scripts to include route extraction

Implementation Details

  • Route parameters are extracted from ASP.NET route templates (e.g., {id}, {name}) and converted to TypeScript function parameters
  • TypeScript routes use camelCase for module names (e.g., backgroundJobs, featureFlags) while maintaining PascalCase in C#
  • All generated files include auto-generated headers to indicate they should not be manually edited
  • Route functions are properly typed with as const for view routes to maintain literal types

https://claude.ai/code/session_01WbxtDa6ZNhLvmyoANe6yi6

claude and others added 4 commits April 5, 2026 15:59
- Add RouteTemplate/HttpMethod fields to EndpointInfoRecord and ViewInfoRecord
- Read `public const string Route` from endpoint classes in SymbolDiscovery
- Create RoutesEmitter generating C# `ModuleRoutes` static class with typed helpers
- Create TypeScriptRoutesEmitter generating TS `routes` const object
- Add SM0054 diagnostic (info) for endpoints missing Route const
- Add tools/extract-routes.mjs and MSBuild ExtractRoutes target
- Move route constants to Contracts projects with Routes nested class
- Add Route/Method const fields to all 185 endpoint classes across 16 modules
- Update Map() calls to reference Route constants instead of string literals
- Make HasAnyRouteTemplates internal static on RoutesEmitter, remove
  duplicate from TypeScriptRoutesEmitter
- Remove dead moduleIndex variable from TypeScriptRoutesEmitter
- Remove unused comma parameter from EmitTsRouteEntry
- Merge two ReadRouteConstFields overloads into one returning a tuple
- Fix regex to handle ASP.NET catch-all params ({**key}) properly
- Apply StripRouteConstraints to static routes too (fixes {**key} leak)
- Simplify BuildInterpolatedRoute to delegate to StripRouteConstraints
- Remove misleading comment
- Use direct file access in extract-routes.mjs instead of readdirSync
C# side: Replace TypedResults.Redirect() hardcoded strings with
ViewPrefix + Routes.* constants in Products, Orders, and Email modules.

TypeScript side: Import routes from @simplemodule/client/routes and
replace hardcoded router.get/post/delete and href strings in Products,
Orders, Tenants, Email, Marketplace, FeatureFlags, and FileStorage.

Infrastructure: Move generated routes.ts to @simplemodule/client
package so all modules can import it. Update extract-routes.mjs and
MSBuild targets to write to the new location.
@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying simplemodule-website with  Cloudflare Pages  Cloudflare Pages

Latest commit: ea92d67
Status: ✅  Deploy successful!
Preview URL: https://65180722.simplemodule-website.pages.dev
Branch Preview URL: https://claude-type-safe-routes-jelu.simplemodule-website.pages.dev

View logs

@antosubash antosubash merged commit ef4e3c3 into main Apr 6, 2026
4 checks passed
@antosubash antosubash deleted the claude/type-safe-routes-JELuV branch April 6, 2026 16:54
antosubash added a commit that referenced this pull request Apr 6, 2026
Auto-merge of PR #87 (type-safe routes) placed the SM0054 endpoint/view
Route-const diagnostics outside the 'foreach (var module in data.Modules)'
loop, leaving 'module' out of scope. Move them back inside the loop.
antosubash added a commit that referenced this pull request Apr 6, 2026
* Fix sm CLI dev workflow and generator diagnostics for user projects

- sm new project: scaffold vite.dev.config.ts so sm dev works out of the box
- sm dev: fix vite invocation (vite dev → vite) and add --configLoader runner
- sm doctor: fix PagesKeyPattern regex to match single-quoted Pages/index.ts keys
- moduleHmrPlugin: fall back to src/modules/ path for scaffolded user projects
- SM0052/SM0053 diagnostics: scope to SimpleModule.* host projects only so user
  projects with their own naming conventions are not falsely flagged
- AgentExtensionsEmitter: remove early return that broke empty projects
- SimpleModuleHostExtensions: register IHttpContextAccessor for EntityInterceptor
- Directory.Build.props: guard PackageReadmeFile with Exists() to fix pack

* Add passkey login design spec

* Update passkey spec: fix API names, schema override, challenge storage

* Add passkey login implementation plan

* feat: add Identity Schema V3 and IdentityPasskeyOptions for passkey support

- Configure IdentityOptions.Stores.SchemaVersion = Version3 in UsersModule to opt into
  the AspNetUserPasskeys table (WebAuthn passkey support)
- Configure IdentityPasskeyOptions from appsettings "Passkeys" section in UsersModule
- Add Passkeys config sections to appsettings.json and appsettings.Development.json
- Fix HostDbContextFactory to expose a minimal service provider with IdentityOptions
  via UseApplicationServiceProvider so EF design-time model creation picks up SchemaVersion
- Add UseApplicationServiceProvider to AddModuleDbContext for runtime parity
- Add migration AddPasskeySupport creating Users_AspNetUserPasskeys table
- Add HostDbContextPasskeys.cs as a seam partial for future passkey customizations

* fix: use SchemaVersion override for passkey schema, revert broad DI changes

Add `protected override Version SchemaVersion => IdentitySchemaVersions.Version3`
to the HostDbContext partial — this compiles cleanly against Identity 10.0.3 and is
the correct way to opt into the AspNetUserPasskeys table. Because SchemaVersion is
now a compile-time constant on the class, the two IOptions workarounds are no longer
needed: remove UseApplicationServiceProvider(sp) from ModuleDbContextOptionsBuilder
(which would have applied the full app DI to every module DbContext) and revert the
ServiceCollection + BuildServiceProvider block from HostDbContextFactory (design-time).

* feat: add PasskeyRegisterBeginEndpoint

Adds the POST /api/passkeys/register/begin endpoint that calls
MakePasskeyCreationOptionsAsync and returns WebAuthn creation options JSON.

Also fixes test infrastructure: UsersDbContext now overrides SchemaVersion
to Version3 so the EF model includes the AspNetUserPasskeys table, and the
shared test factory now calls UseApplicationServiceProvider so IdentityOptions
(including SchemaVersion) are accessible during model creation. Adds
appsettings.Testing.json to configure ServerDomain = "localhost" for tests.

* feat: add PasskeyRegisterCompleteEndpoint

Adds the POST /api/passkeys/register/complete endpoint that calls
PerformPasskeyAttestationAsync to validate WebAuthn attestation and stores
the passkey. Catches InvalidOperationException (no challenge cookie) and
PasskeyException (crypto failure) to return 400 instead of 500.

Also adds PasskeyApiEndpointTests integration test class covering all four
scenarios: RegisterBegin authenticated/unauthenticated and RegisterComplete
unauthenticated/invalid-credential. Tests seed a real user (passkey-test-user-id)
with CreateAuthenticatedClient targeting that user ID.

* fix: dispose StreamReader and verify PasskeyException handling in RegisterCompleteEndpoint

Replace undisposed StreamReader with a using block (leaveOpen: true) to avoid
closing the framework-owned request body stream. PasskeyException confirmed
present in .NET 10 Identity — catch block retained as-is.

* feat: add passkey login and management API endpoints (Tasks 4-7)

Add PasskeyLoginBeginEndpoint, PasskeyLoginCompleteEndpoint, GetPasskeysEndpoint,
and DeletePasskeyEndpoint with full integration test coverage (38 tests passing).

* feat: add ManagePasskeysEndpoint (Inertia view)

* feat: add WebAuthn browser API utility (passkey.ts)

* feat: add ManagePasskeys React page

* feat: add passkey sign-in button to login page

* feat: pass passkeyEnabled prop from IdentityPasskeyOptions to login page

* feat: register ManagePasskeys page and add Passkeys nav item to account sidebar

* feat: add device type hint to passkeys list

Adds transports field to the passkeys DTO and derives a human-readable
device type hint (e.g. 'Built-in sensor', 'Security key (USB)') shown
below the passkey name in the Manage Passkeys page.

* refactor: fix passkeyEnabled in login error path, extract ToBase64Url helper

* style: exclude test-projects from biome root config scan

The test-projects directory contains its own biome.json which conflicts
with the root configuration. Since test-projects is already excluded
from the files.includes scope, explicitly adding it as an ignore pattern
prevents the nested root config error.

* test: add e2e tests for passkey registration, sign-in, and management

* fix: make passkey e2e tests reliable across parallel runs and page navigations

- serial mode + beforeEach cleanup eliminates shared-state races between tests
- migrate credential ID via CDP WebAuthn.getCredentials after registration so the
  sign-in assertion works whether or not the credential was stored as a resident key
- register route handlers before navigation so no request can slip through unintercepted
- replace page.waitForTimeout(500) with try/catch on waitForURL for deterministic error reporting
- fix auth.setup.ts to navigate directly to /Identity/Account/Login instead of
  clicking the missing "Log in" link on the landing page

* fix: restore SM0054 loop inside foreach module after merge with main

Auto-merge of PR #87 (type-safe routes) placed the SM0054 endpoint/view
Route-const diagnostics outside the 'foreach (var module in data.Modules)'
loop, leaving 'module' out of scope. Move them back inside the loop.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants