Skip to content

Commit 637565e

Browse files
committed
feat: add modelsdk — auto-generated, type-safe model SDK with BSON roundtrip
Add a new `modelsdk/` package that provides a fundamentally better architecture for reading and writing Mendix MPR files compared to the existing hand-written `sdk/` layer. This is an additive, non-breaking change — no existing code is modified. The new package coexists with `sdk/` to enable gradual migration. Key improvements over current sdk/: - 1,500+ auto-generated types across 53 Mendix domains (vs ~480 hand-written) - Type registry with automatic init() registration (vs manual dispatch) - Dirty tracking via bitmap + container chain propagation - BSON roundtrip preservation — unknown fields survive read/write cycles - Lazy decode via InitFromRaw() — zero cost for unaccessed fields - Three-branch encoder: self-dirty / child-dirty / clean pass-through - Property abstraction: Primitive[T], Part[T], PartList[T], Enum[T], ByNameRef[T] - Per-property version metadata (introduced/deleted/public) - Reference registry for cross-domain relationship tracking Packages added: - modelsdk/codec — BSON encoder/decoder with type registry - modelsdk/element — Element interface with dirty tracking - modelsdk/property — Generic property types with lazy init - modelsdk/gen/ — 53 auto-generated domain packages - modelsdk/mpr — MPR v1/v2 reader/writer - modelsdk/widgets — Widget template handling - modelsdk/meta — System module metadata - modelsdk/version — Version compatibility info - cmd/modelsdk-codegen — Code generator from TS SDK reflection data - internal/codegen/dtsparser — TypeScript SDK parser - internal/codegen/emitter — Go code generation templates
1 parent d871691 commit 637565e

327 files changed

Lines changed: 198921 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cmd/modelsdk-codegen/audit.go

Lines changed: 481 additions & 0 deletions
Large diffs are not rendered by default.

cmd/modelsdk-codegen/main.go

Lines changed: 415 additions & 0 deletions
Large diffs are not rendered by default.

internal/codegen/dtsparser/jsparser.go

Lines changed: 755 additions & 0 deletions
Large diffs are not rendered by default.

internal/codegen/dtsparser/jsparser_test.go

Lines changed: 452 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package dtsparser
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"strings"
8+
"testing"
9+
)
10+
11+
func TestParseAllDomains(t *testing.T) {
12+
genDir := "../../../reference/mendixmodelsdk/src/gen"
13+
if _, err := os.Stat(genDir); os.IsNotExist(err) {
14+
t.Skip("reference/mendixmodelsdk not available")
15+
}
16+
17+
// Collect all enums across modules
18+
allEnums := collectCrossModuleEnums(genDir)
19+
t.Logf("Cross-module enums collected: %d", len(allEnums))
20+
21+
entries, err := os.ReadDir(genDir)
22+
if err != nil {
23+
t.Fatalf("cannot read gen dir: %v", err)
24+
}
25+
26+
totalClasses := 0
27+
totalEnums := 0
28+
totalProps := 0
29+
totalStructTypeNames := 0
30+
kindCounts := map[PropertyKind]int{}
31+
domainSummary := []string{}
32+
33+
for _, entry := range entries {
34+
if !strings.HasSuffix(entry.Name(), ".d.ts") {
35+
continue
36+
}
37+
domain := strings.TrimSuffix(entry.Name(), ".d.ts")
38+
if domain == "base-model" || domain == "all-model-classes" {
39+
continue // meta files, not domain files
40+
}
41+
42+
dtsData, err := os.ReadFile(filepath.Join(genDir, entry.Name()))
43+
if err != nil {
44+
t.Errorf("cannot read %s: %v", entry.Name(), err)
45+
continue
46+
}
47+
48+
classes, enums := parseDtsFileWithEnums(string(dtsData), allEnums)
49+
50+
// Also parse .js for structureTypeNames
51+
jsFile := strings.TrimSuffix(entry.Name(), ".d.ts") + ".js"
52+
jsData, _ := os.ReadFile(filepath.Join(genDir, jsFile))
53+
stns := parseStructureTypeNames(string(jsData))
54+
55+
domainClasses := len(classes)
56+
domainEnums := len(enums)
57+
domainProps := 0
58+
for _, c := range classes {
59+
domainProps += len(c.Properties)
60+
for _, p := range c.Properties {
61+
kindCounts[p.Kind]++
62+
}
63+
}
64+
65+
totalClasses += domainClasses
66+
totalEnums += domainEnums
67+
totalProps += domainProps
68+
totalStructTypeNames += len(stns)
69+
70+
domainSummary = append(domainSummary,
71+
fmt.Sprintf("%-30s classes=%3d enums=%2d props=%4d $Types=%3d",
72+
domain, domainClasses, domainEnums, domainProps, len(stns)))
73+
}
74+
75+
t.Log("=== Per-Domain Summary ===")
76+
for _, s := range domainSummary {
77+
t.Log(s)
78+
}
79+
80+
t.Log("=== Totals ===")
81+
t.Logf("Domains: %d", len(domainSummary))
82+
t.Logf("Total classes: %d", totalClasses)
83+
t.Logf("Total enums: %d", totalEnums)
84+
t.Logf("Total properties: %d", totalProps)
85+
t.Logf("Total $Type names: %d", totalStructTypeNames)
86+
87+
t.Log("=== Property Kind Distribution ===")
88+
for k, v := range kindCounts {
89+
t.Logf(" %-10s: %4d (%.1f%%)", k, v, float64(v)/float64(totalProps)*100)
90+
}
91+
92+
// Classification rate
93+
unknownPct := float64(kindCounts[KindUnknown]) / float64(totalProps) * 100
94+
classRate := 100 - unknownPct
95+
t.Logf("Classification rate: %.1f%%", classRate)
96+
97+
// Assertions
98+
if len(domainSummary) < 40 {
99+
t.Errorf("expected at least 40 domains, got %d", len(domainSummary))
100+
}
101+
if totalClasses < 200 {
102+
t.Errorf("expected at least 200 classes, got %d", totalClasses)
103+
}
104+
if classRate < 80 {
105+
t.Errorf("classification rate %.1f%% too low, expected >= 80%%", classRate)
106+
}
107+
}

0 commit comments

Comments
 (0)