Skip to content

Commit 00ac646

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 00ac646

327 files changed

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

0 commit comments

Comments
 (0)