Skip to content

Context Mapping & Additional Generators

Two related features:

  1. Context parameter mappings — map database parameters (like user_id, created_by, tenant_id) to a context object, so they don't clutter every call site.
  2. Additional generators — generate extra outputs (TypeScript, providers, controllers, …) from the same metadata, in one pass.

Context parameter mappings

The problem

Many database functions require authentication/context parameters — userid, tenantid, createdby, and so on. Passing them explicitly at every call is noisy and error-prone. Instead, map them to a context object.

Configuration

{
  "UseUserContext": true,
  "UserContextParameterName": "ctx",
  "UserContextType": "UserContext",
  "ContextParameterMappings": [
    { "ParameterNames": ["userid"], "ContextPath": "User.UserId" },
    { "ParameterNames": ["displaylanguagecode"], "ContextPath": "Locale" },
    { "ParameterNames": ["createdby", "updatedby", "deletedby"], "ContextPath": "Username" },
    { "ParameterNames": ["tenantid"], "ContextPath": "SelectedTenant.TenantId" }
  ]
}

What it changes

public async Task<List<User>> GetUsersAsync(int userId, int tenantId, string searchTerm)
{
    return await dbContext.GetUsersAsync(userId, tenantId, searchTerm);
}
public async Task<List<User>> GetUsersAsync(UserContext ctx, string searchTerm)
{
    return await dbContext.GetUsersAsync(ctx.User.UserId, ctx.SelectedTenant.TenantId, searchTerm);
}

Template access

Each routine exposes the split between context and regular parameters:

{{range $routine := .Functions}}
  {{- if $routine.UsesUserContext}}
    {{range $param := $routine.ContextParameters}}
      // context: {{$param.PropertyName}} -> {{$param.ContextPath}}
    {{end}}
    {{range $param := $routine.RegularParameters}}
      // regular: {{$param.PropertyName}}
    {{end}}
  {{- end}}
{{end}}

Language-agnostic

The configuration provides data; templates decide the syntax:

{{$.Config.UserContextParameterName}}.{{$param.ContextPath}}
// -> ctx.User.UserId
%{{$.Config.UserContextType}}{} = {{$.Config.UserContextParameterName}}
{{$.Config.UserContextParameterName}}.{{$param.ContextPath}}
# -> %UserContext{} = ctx ; ctx.user.user_id

Additional generators

The problem

Previously you needed a separate tool (e.g. a C# model generator) to produce TypeScript models, providers, or other code. db-gen can generate all of it in one pass, reusing its internal metadata.

Configuration

{
  "AdditionalGenerators": [
    {
      "Name": "TypeScript",
      "Enabled": true,
      "Template": "./templates/typescript.gotmpl",
      "OutputFolder": "./output/typescript",
      "FileExtension": ".ts",
      "FileCase": "camelcase",
      "GenerationType": "per-routine"
    },
    {
      "Name": "Provider",
      "Enabled": true,
      "Template": "./templates/provider.gotmpl",
      "OutputFolder": "./output",
      "FileName": "CommonProvider.cs",
      "GenerationType": "single-file"
    }
  ]
}

Generation types

Type Template data Output Use for
per-routine ModelTemplateData (one Routine) one file per routine, named routine + FileExtension TypeScript interfaces, individual controllers
single-file DbContextData (all Functions) one file named FileName CommonProvider, SignalR hub, aggregated index

If GenerationType is omitted, db-gen infers single-file when FileName is set, otherwise per-routine.

Option Required Description
Name yes Display name for logging
Enabled yes Enable/disable
Template yes Path to the .gotmpl file
OutputFolder yes Where to write
FileName for single-file Output file name
FileExtension for per-routine Output extension
FileCase optional snakecase / camelcase / pascalcase
GenerationType optional per-routine / single-file (auto-detected)
CleanOutputFolder optional Delete the output folder before generating

Example templates

export interface {{.Routine.ModelName}} {
{{- range $property := .Routine.ReturnProperties}}
  {{camelCased $property.PropertyName}}: {{if eq $property.PropertyType "int"}}number{{else}}any{{end}};
{{- end}}
}
public class CommonProvider
{
{{range $routine := .Functions}}
  public async Task<List<{{$routine.ModelName}}>> {{$routine.FunctionName}}Async(
    {{if $routine.UsesUserContext}}{{$.Config.UserContextType}} {{$.Config.UserContextParameterName}}{{end}}
    {{range $param := $routine.RegularParameters}}, {{$param.PropertyType}} {{$param.PropertyName}}{{end}})
  {
    return await dbContext.{{$routine.FunctionName}}Async(
      {{range $param := $routine.Parameters}}
        {{if $param.IsContextParameter}}{{$.Config.UserContextParameterName}}.{{$param.ContextPath}}{{else}}{{$param.PropertyName}}{{end}},
      {{end}});
  }
{{end}}
}

Why it's powerful

  • Single source of truth — database functions drive every output.
  • No re-parsing — uses db-gen's internal structures directly.
  • Consistency — all outputs match the database schema.
  • Extensibility — add a generator without changing code.

The same mechanism can produce ASP.NET controllers, SignalR hubs, Phoenix channels, GraphQL schemas, OpenAPI specs, test fixtures, or client SDKs in any language.