Module Definition Files
Module: github.com/redelay/go-framework/core/schema, github.com/redelay/go-framework/core/modfile
Redelay modules can be defined as YAML or JSON files, enabling cross-language module definitions and IDE-assisted editing with JSON Schema validation. This is the recommended approach for modules shared across Go, Python, and JavaScript implementations.
Overview
Every module in Redelay maps to an ir.Module — the canonical intermediate representation.
Modules can be defined in three ways:
| Approach | Use case | Cross-language |
|---|---|---|
| YAML/JSON files | Shared module definitions | Yes — any language |
| Programmatic (Go) | Go-native modules with runtime logic | Go only |
| Import from specs | Derive from OpenAPI, AsyncAPI, FlowDSL | Yes — via importers |
All three paths produce the same ir.Module structure that feeds into the compile pipeline.
Module file format
A module definition file is a direct YAML/JSON serialization of ir.Module. All field
names use snake_case matching the Go struct json tags.
Minimal example
# yaml-language-server: $schema=https://redelay.com/schemas/module.schema.json
id: notifications
name: Notifications
description: Email and push notification module
dependencies:
- users
Full example
id: auth
name: Auth
description: JWT authentication and token management
version: 1.0.0
dependencies:
- users
entities:
- id: auth.refresh_token
name: RefreshToken
fields:
- name: token_id
type: string
required: true
- name: user_id
type: string
required: true
- name: expires_at
type: datetime
required: true
events:
- id: auth.login
name: auth.login
entity_type: user
action: login
topic: auth
payload:
id: auth.login.payload
name: AuthLoginPayload
fields:
- name: user_id
type: string
required: true
- name: email
type: string
required: true
- id: auth.login_failed
name: auth.login_failed
entity_type: user
action: login_failed
topic: auth
config:
id: auth
name: Auth Configuration
entries:
- key: AUTH_JWT_SECRET
type: string
required: true
sensitive: true
- key: AUTH_JWT_ALGORITHM
type: string
default: HS256
- key: AUTH_ACCESS_TOKEN_TTL_MINUTES
type: integer
default: 30
settings:
id: auth
name: Authentication Settings
groups:
- id: auth.tokens
label: Token Settings
order: 1
settings:
- key: access_token_ttl_minutes
label: Access Token TTL (min)
type: integer
default: 30
- key: refresh_token_ttl_days
label: Refresh Token TTL (days)
type: integer
default: 7
crud:
- id: auth.refresh_token
entity_ref: auth.refresh_token
operations:
- kind: create
emits: [auth.token_refreshed]
- kind: delete
emits: [auth.token_revoked]
JSON Schema
JSON Schema files (Draft 2020-12) are generated from the Go IR types via reflection:
| Schema | Validates | URI |
|---|---|---|
module.schema.json | Single module definition | https://redelay.com/schemas/module.schema.json |
document.schema.json | Full IR document | https://redelay.com/schemas/document.schema.json |
VS Code setup
Add to .vscode/settings.json for automatic validation and autocomplete:
{
"yaml.schemas": {
"https://redelay.com/schemas/module.schema.json": [
"**/modules/*.yaml",
"**/modules/*.yml"
],
"https://redelay.com/schemas/document.schema.json": [
"**/*.redelay.yaml"
]
}
}
Or add an inline schema reference at the top of any YAML file:
# yaml-language-server: $schema=https://redelay.com/schemas/module.schema.json
Generating schemas
Schemas are generated from Go IR types via reflection. The core/schema package
provides both programmatic and JSON output:
import "github.com/redelay/go-framework/core/schema"
// Get module schema as Go map
s := schema.ModuleSchema()
// Get as formatted JSON bytes
data, err := schema.ModuleSchemaJSON()
// Get document schema
data, err := schema.DocumentSchemaJSON()
Schema generator
The underlying schema.Generator can produce JSON Schema for any Go struct type:
g := schema.New()
g.RegisterEnum(MyKind(""), []string{"a", "b", "c"})
data, err := g.JSON(MyStruct{}, "https://example.com/my.schema.json", "My Schema")
Features:
- Maps Go primitives to JSON Schema types (
string,integer,number,boolean) - Handles
time.Timeasstringwithdate-timeformat - Named struct types become
$defsentries with$refpointers - Recursive types handled via cycle detection
- Embedded structs flattened into parent properties
jsontags control property names;omitemptycontrols required- Pointer types treated as optional (not required)
- Registered enum types emit
enumconstraints map[string]TbecomesobjectwithadditionalPropertiesany/interface{}becomes an unconstrained schema
Field types
The type field in entity/schema/packet fields accepts these values:
| Type | Description | JSON Schema equivalent |
|---|---|---|
string | Text value | string |
integer | Whole number | integer |
float | Decimal number | number |
boolean | True/false | boolean |
array | List (use items for element type) | array |
object | Nested object (use properties) | object |
date | Date without time | string + date |
datetime | Date with time | string + date-time |
uuid | UUID string | string + uuid |
binary | Binary data | string + binary |
ref | Reference to another entity | string + $ref |
enum | Enumerated value (use enum list) | string + enum |
any | Any type | unconstrained |
Loading and saving module files
The core/modfile package provides load/save for both module and document files:
import "github.com/redelay/go-framework/core/modfile"
// Load a module from YAML
f, _ := os.Open("modules/auth.yaml")
mod, err := modfile.LoadModule(f, modfile.DetectFormat("auth.yaml"))
// Load from JSON
mod, err := modfile.LoadModule(r, "json")
// Save as YAML
modfile.SaveModule(mod, w, "yaml")
// Load a full document
doc, err := modfile.LoadDocument(r, "yaml")
// Format detection from filename
format := modfile.DetectFormat("auth.yaml") // "yaml"
format := modfile.DetectFormat("auth.json") // "json"
Exporting and loading module directories
The modfile package supports batch operations for working with directories of module
files — the standard layout for an application's module definitions:
import "github.com/redelay/go-framework/core/modfile"
// Export modules as individual YAML files to a directory.
// Each module becomes "<id>.module.yaml".
paths, err := modfile.ExportModules(modules, "modules/")
// Returns: ["modules/auth.module.yaml", "modules/users.module.yaml", ...]
// Load all module files from a directory.
// Reads *.module.yaml, *.module.yml, *.module.json in alphabetical order.
modules, err := modfile.LoadDir("modules/")
Generating module files from registered modules
Use app.BootstrapIR() to extract IR from all registered Go modules without
connecting to databases or starting the application:
import (
_ "github.com/redelay/go-framework/modules/auth"
_ "github.com/redelay/go-framework/modules/users"
"github.com/redelay/go-framework/app"
"github.com/redelay/go-framework/core/modfile"
)
application, _ := app.BootstrapIR("myapp")
doc := application.IR()
modfile.ExportModules(doc.Modules, "modules/")
Or use the CLI:
# Export an IR document's modules as individual YAML files
redelayctl generate ir-document.yaml modules/
Backend generate command pattern
Each application can have a cmd/generate/ entry point that produces
its module YAML files. This enables regeneration whenever core modules
are updated:
// backend/cmd/generate/main.go
package main
import (
_ "github.com/redelay/go-framework/modules/auth"
_ "github.com/redelay/go-framework/modules/users"
// ... more module imports ...
"github.com/redelay/go-framework/app"
"github.com/redelay/go-framework/core/modfile"
)
func main() {
application, _ := app.BootstrapIR("myapp")
doc := application.IR()
modfile.ExportModules(doc.Modules, "modules/")
}
Run with go run ./cmd/generate/ to (re)generate all module YAML files.
Extending core modules
Applications often need to add events, entities, or config to core modules
without forking them. The modfile.MergeModule() function provides an overlay
mechanism:
import "github.com/redelay/go-framework/core/modfile"
// Load the core module (generated or from framework)
base, _ := modfile.LoadModule(baseReader, "yaml")
// Load app-level overlay with additions
overlay, _ := modfile.LoadModule(overlayReader, "yaml")
// Merge: overlay augments the base
merged := modfile.MergeModule(base, overlay)
Merge semantics
| Field type | Behavior |
|---|---|
| Scalars (description, version) | Overlay replaces base if non-empty |
| String slices (dependencies, provides, imports) | Deduplicated union |
| Struct slices (entities, events, commands, ...) | Appended from overlay |
| Config entries | Merged by key — overlay wins on conflict, new entries appended |
| Setting groups | Merged by ID — overlay wins on conflict, new groups appended |
Example: extending auth with MFA
Create modules/auth.overlay.yaml:
id: auth
events:
- id: auth.mfa_verified
name: auth.mfa_verified
entity_type: user
action: mfa_verified
topic: auth
config:
id: auth
entries:
- key: AUTH_MFA_ENABLED
type: boolean
default: false
- key: AUTH_MFA_ISSUER
type: string
default: "MyApp"
Load and merge at startup:
base, _ := modfile.LoadModule(openBase, "yaml")
overlay, _ := modfile.LoadModule(openOverlay, "yaml")
merged := modfile.MergeModule(base, overlay)
// merged now has all original auth events + auth.mfa_verified,
// all original config entries + AUTH_MFA_ENABLED, AUTH_MFA_ISSUER
Cross-language usage
Module definition files are standard YAML/JSON matching the IR schema. Any language with a YAML/JSON parser can read them:
Python:
import yaml
with open("modules/auth.yaml") as f:
module = yaml.safe_load(f)
print(module["events"][0]["name"]) # "auth.login"
JavaScript:
import { readFileSync } from 'fs';
import { parse } from 'yaml';
const module = parse(readFileSync('modules/auth.yaml', 'utf8'));
console.log(module.events[0].name); // "auth.login"
Integration with compile pipeline
Module files integrate with the existing compile pipeline:
import (
"github.com/redelay/go-framework/core/compile"
"github.com/redelay/go-framework/core/ir"
"github.com/redelay/go-framework/core/modfile"
)
// Load external module definitions
f, _ := os.Open("modules/notifications.yaml")
mod, _ := modfile.LoadModule(f, "yaml")
// Combine with programmatic modules
doc := ir.NewDocument("MyApp", "1.0")
doc.Modules = append(doc.Modules, mod)
// Compile (validate + resolve + enrich)
result, err := compile.Compile(doc)
Integration with external specs
Module files can coexist with imported specs. Use importers to derive modules from OpenAPI, AsyncAPI, or FlowDSL documents, then merge with explicit module definitions:
// Import an AsyncAPI spec -> IR events/channels
asyncDoc, _ := compile.Import("asyncapi", asyncReader)
// Load explicit module definition
mod, _ := modfile.LoadModule(modReader, "yaml")
// Merge into a single document
doc := ir.NewDocument("MyApp", "1.0")
doc.Modules = append(doc.Modules, mod)
doc.Channels = asyncDoc.Channels
doc.Events = append(doc.Events, asyncDoc.Events...)
result, _ := compile.Compile(doc)
SVG diagram generation
Module: github.com/redelay/go-framework/core/modsvg
Generate visual SVG diagrams from module definitions. The diagram shows the module center with surrounding cards for each section (entities, events, config, CRUD, settings, commands, queries, workflows, schedules, consumers, dependencies, provides).
CLI usage
# Output to stdout
redelayctl diagram auth.module.yaml
# Output to file
redelayctl diagram auth.module.yaml auth-diagram.svg
Programmatic usage (Go)
import (
"os"
"github.com/redelay/go-framework/core/modsvg"
"github.com/redelay/go-framework/core/modfile"
)
f, _ := os.Open("auth.module.yaml")
mod, _ := modfile.LoadModule(f, modfile.FormatYAML)
// Write to file
out, _ := os.Create("auth.svg")
modsvg.Generate(out, mod)
// Or get as string
svg, _ := modsvg.GenerateString(mod)
Features
- Hub-and-spoke layout with module name and version at center
- Color-coded section cards matching the Redelay brand palette
- Badge counts showing number of items per section
- Dark mode support via
prefers-color-schemeCSS media query - Drop shadow effects for depth
- SVG viewBox for responsive scaling
Module spec UI
Module: github.com/redelay/go-framework/modules/modspec
The modspec module provides an interactive HTML page listing all registered modules
with SVG diagrams and YAML spec views — similar to how the openapi module serves
Scalar/Swagger UI for API reference.
Endpoints
| Path | Content-Type | Description |
|---|---|---|
/modules.json | application/json | JSON array of all module specs with SVG + YAML |
/modules | text/html | Interactive module browser UI |
Configuration
| Env var | Default | Description |
|---|---|---|
MODSPEC_SPEC_PATH | /modules.json | Path for the JSON spec endpoint |
MODSPEC_UI_PATH | /modules | Path for the HTML UI page |
Usage
Add a blank import to your application entry point:
import _ "github.com/redelay/go-framework/modules/modspec"
Then visit /modules for the interactive browser or /modules.json for the raw JSON spec.
Module tooling pipeline
Module YAML files are the single source of truth for the full development workflow. Three core packages power validation, scaffolding, and drift detection:
| Package | CLI command | Purpose |
|---|---|---|
core/modval | redelayctl validate-module | Validate naming conventions, reference integrity, required fields |
core/modgen | redelayctl scaffold | Generate Go module directory (factory, routes, handlers) |
core/modsync | redelayctl audit | Compare declared YAML vs runtime module for drift |
modval — Module validator
Module: github.com/redelay/go-framework/core/modval
Validates an ir.Module against domain-specific rules:
- Required fields — ID and name must be present
- Version format — must be valid semver (e.g.
1.0.0) - Entity IDs — must be prefixed with module ID (
auth.token, nottoken) - Event names — must contain a dot separator (
auth.login) - Config keys — must be
UPPER_SNAKE_CASEwith module prefix (AUTH_JWT_SECRET) - Settings keys — must be
lower_snake_case - CRUD refs — entity references must match declared entities
- No duplicates — entity and event IDs must be unique
- No self-dependencies — a module cannot depend on itself
Diagnostic codes use the MV prefix (e.g. MV001, MV010, MV021).
import "github.com/redelay/go-framework/core/modval"
diag := modval.Validate(module)
if diag.HasErrors() {
for _, e := range diag.Errors() {
fmt.Printf("ERROR [%s] %s\n", e.Code, e.Message)
}
}
modgen — Module scaffolder
Module: github.com/redelay/go-framework/core/modgen
Generates a working Go module directory from an ir.Module definition:
| Generated file | Content |
|---|---|
module.go | init() factory, Module struct with modules.IRBase, Startup/Shutdown stubs |
routes.go | HTTP route registration from CRUD operations (only if module has CRUD) |
handlers.go | Handler documentation stubs (only if module has CRUD) |
import "github.com/redelay/go-framework/core/modgen"
result, err := modgen.Scaffold(module, modgen.Options{
OutputDir: "./modules/orders",
Package: "orders", // defaults to module ID
Force: false, // don't overwrite existing files
})
for _, f := range result.Files {
fmt.Println(f) // list generated files
}
The scaffolder is designed for one-time generation: it creates the boilerplate once,
then you implement business logic in the generated stubs. Existing files are preserved
unless Force is set.
modsync — Drift detection
Module: github.com/redelay/go-framework/core/modsync
Compares two ir.Module instances and produces a structured drift report. Typically
used to compare a declared YAML definition against a runtime-exported module.
import "github.com/redelay/go-framework/core/modsync"
report := modsync.Compare(declared, actual)
if report.HasDrift() {
fmt.Print(report.Summary())
for _, d := range report.Diffs {
fmt.Println(d.String()) // e.g. "[+] events: auth.mfa_enabled"
}
}
Drift kinds:
| Kind | Symbol | Meaning |
|---|---|---|
added | + | Present in actual, missing from declared |
removed | - | Present in declared, missing from actual |
changed | ~ | Present in both but different (e.g. field count) |
Compared sections: name, version, description, entities, events, config, settings, CRUD, dependencies, provides, commands, queries, workflows, schedules, consumers.