@xrmforge/typegen 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/MIGRATION.md +40 -11
  2. package/dist/index.d.ts +15 -27
  3. package/dist/index.js +191 -206
  4. package/dist/index.js.map +1 -1
  5. package/docs/architecture/00-README.md +26 -0
  6. package/docs/architecture/01-executive-summary.md +11 -0
  7. package/docs/architecture/02-packages.md +110 -0
  8. package/docs/architecture/03-generated-types.md +172 -0
  9. package/docs/architecture/04-cli.md +58 -0
  10. package/docs/architecture/05-build.md +50 -0
  11. package/docs/architecture/06-incremental.md +42 -0
  12. package/docs/architecture/07-http-client.md +59 -0
  13. package/docs/architecture/08-authentication.md +18 -0
  14. package/docs/architecture/09-testing.md +55 -0
  15. package/docs/architecture/10-eslint-plugin.md +82 -0
  16. package/docs/architecture/11-agent-md.md +38 -0
  17. package/docs/architecture/12-xrm-pitfalls.md +14 -0
  18. package/docs/architecture/13-helpers.md +50 -0
  19. package/docs/architecture/14-showcases.md +21 -0
  20. package/docs/architecture/15-ci-cd.md +49 -0
  21. package/docs/architecture/16-technical-debt.md +17 -0
  22. package/docs/architecture/17-roadmap.md +25 -0
  23. package/docs/architecture/18-design-principles.md +22 -0
  24. package/docs/architektur/00-README.md +26 -0
  25. package/docs/architektur/01-zusammenfassung.md +11 -0
  26. package/docs/architektur/02-packages.md +110 -0
  27. package/docs/architektur/03-generierte-typen.md +172 -0
  28. package/docs/architektur/04-cli.md +58 -0
  29. package/docs/architektur/05-build.md +50 -0
  30. package/docs/architektur/06-inkrementell.md +42 -0
  31. package/docs/architektur/07-http-client.md +59 -0
  32. package/docs/architektur/08-authentifizierung.md +18 -0
  33. package/docs/architektur/09-testing.md +55 -0
  34. package/docs/architektur/10-eslint-plugin.md +82 -0
  35. package/docs/architektur/11-agent-md.md +38 -0
  36. package/docs/architektur/12-xrm-fallstricke.md +14 -0
  37. package/docs/architektur/13-helpers.md +50 -0
  38. package/docs/architektur/14-showcases.md +21 -0
  39. package/docs/architektur/15-ci-cd.md +49 -0
  40. package/docs/architektur/16-technische-schulden.md +17 -0
  41. package/docs/architektur/17-roadmap.md +25 -0
  42. package/docs/architektur/18-designprinzipien.md +22 -0
  43. package/package.json +4 -3
@@ -0,0 +1,82 @@
1
+ # ESLint Plugin
2
+
3
+ ### 10.1 Installation
4
+
5
+ ```javascript
6
+ // eslint.config.js (flat config, ESLint v9)
7
+ import xrmforge from '@xrmforge/eslint-plugin';
8
+
9
+ export default [
10
+ xrmforge.configs.recommended,
11
+ // or pick individual rules
12
+ ];
13
+ ```
14
+
15
+ ### 10.2 Rules
16
+
17
+ #### no-xrm-page (error)
18
+
19
+ Forbids the deprecated `Xrm.Page` API (removed in D365 v9.0+).
20
+
21
+ ```typescript
22
+ // Bad
23
+ Xrm.Page.getAttribute("name");
24
+
25
+ // Good
26
+ const form = executionContext.getFormContext();
27
+ form.getAttribute("name");
28
+ ```
29
+
30
+ #### no-magic-optionset (warn)
31
+
32
+ Forbids raw numbers (>= 2) in comparisons with `.getValue()`.
33
+
34
+ ```typescript
35
+ // Bad
36
+ if (attr.getValue() === 595300000) { }
37
+
38
+ // Good
39
+ import { StatusCode } from '../generated/optionsets/account';
40
+ if (attr.getValue() === StatusCode.Active) { }
41
+ ```
42
+
43
+ #### no-sync-webapi (error)
44
+
45
+ Forbids synchronous XMLHttpRequest (`new XMLHttpRequest()` and `.open()` with `async=false`).
46
+
47
+ ```typescript
48
+ // Bad
49
+ xhr.open("GET", url, false);
50
+
51
+ // Good
52
+ const data = await Xrm.WebApi.retrieveRecord("account", id);
53
+ ```
54
+
55
+ #### require-error-handling (warn)
56
+
57
+ Requires try/catch in exported async functions starting with "on" (event handlers).
58
+
59
+ ```typescript
60
+ // Bad
61
+ export async function onLoad(ctx) {
62
+ await fetch("/api"); // no error handling
63
+ }
64
+
65
+ // Good
66
+ export async function onLoad(ctx) {
67
+ try { await fetch("/api"); }
68
+ catch (error) { console.error(error); }
69
+ }
70
+ ```
71
+
72
+ #### require-namespace (warn)
73
+
74
+ Forbids direct `window.X = ...` or `globalThis.X = ...` assignments. Module exports with esbuild globalName should be used instead.
75
+
76
+ ```typescript
77
+ // Bad
78
+ window.Contoso = { onLoad: function() {} };
79
+
80
+ // Good
81
+ export function onLoad(ctx: Xrm.Events.EventContext) {}
82
+ ```
@@ -0,0 +1,38 @@
1
+ # AGENT.md System
2
+
3
+ ### 11.1 Purpose
4
+
5
+ The AGENT.md is a scaffolded file that teaches AI coding assistants (Claude, ChatGPT, Copilot, Cursor) how to write optimal D365 form scripts using XrmForge. It is generated by `xrmforge init` and placed in the project root.
6
+
7
+ ### 11.2 Content Structure
8
+
9
+ 1. **Package overview** - What each @xrmforge package does
10
+ 2. **10 Rules: Always** - Fields Enum, OptionSet Enum, FormContext cast, EntityNames, parseLookup, select, createFormMock, module exports, Tabs/Sections enums, error handling
11
+ 3. **Rules: Never** - Raw strings, magic numbers, Xrm.Page, sync XHR, eval, window assignments
12
+ 4. **Before/After examples** - Field access, OptionSet comparison, testing
13
+ 5. **Pattern Recognition table** - Legacy pattern to XrmForge replacement mapping
14
+ 6. **OptionSet Enum creation guide** - How to create enums from magic numbers in legacy code
15
+ 7. **Testing with setupXrmMock** - Global Xrm mock pattern
16
+ 8. **Build commands** - xrmforge build, watch mode
17
+ 9. **@types/xrm Pitfalls** - Known issues and workarounds
18
+ 10. **File structure** - Expected project layout
19
+
20
+ ### 11.3 Template System
21
+
22
+ The AGENT.md is stored as `src/scaffold/templates/AGENT.md` in the devkit package and loaded via `template-loader.ts` at scaffold time. No variable substitution needed (the file is static).
23
+
24
+ ### 11.4 KI Comparison Test Results
25
+
26
+ Five AI models were tested converting legacy D365 JavaScript (account.js + lm_helper.js, 1,288 lines) to TypeScript with XrmForge:
27
+
28
+ | Rank | Model | Score | Tool | Strength |
29
+ |------|-------|-------|------|----------|
30
+ | 1 | Claude Opus 4.6 | 42/50 | Claude Code | Most tests (62), best code structure |
31
+ | 2 | Claude Sonnet 4.6 | 41/50 | Claude Code | Most bugs found (5), best DI approach |
32
+ | 3 | Cursor Composer 2 | 35/50 | Cursor IDE | Recognized select() Node API issue |
33
+ | 4 | ChatGPT GPT-4o | 30/50 | ChatGPT Web | Functional but less XrmForge-specific |
34
+ | 5 | MS Copilot | 12/50 | Browser Chat | No workspace access, never saw AGENT.md |
35
+
36
+ **Criteria (11, max 5 points each = 55 max):** Fields Enum usage, OptionSet Enums, FormContext typing, XrmForge helpers, module exports, tests present, test quality, error handling, code quality, bugs found, documentation.
37
+
38
+ **Key finding:** No AI consistently used `@xrmforge/helpers` imports (select, parseLookup). This remains the biggest adoption gap.
@@ -0,0 +1,14 @@
1
+ # @types/xrm Pitfalls
2
+
3
+ Known issues when working with `@types/xrm`:
4
+
5
+ | Issue | Wrong | Correct |
6
+ |-------|-------|---------|
7
+ | Form interface | `interface extends Xrm.FormContext` | `extends Omit<Xrm.FormContext, 'getAttribute' \| 'getControl'>` |
8
+ | AlertDialogResponse | `Xrm.Navigation.AlertDialogResponse` | `Xrm.Async.PromiseLike<void>` (type does not exist) |
9
+ | ConfirmDialogResponse | `Xrm.Navigation.ConfirmDialogResponse` | `Xrm.Navigation.ConfirmResult` (type does not exist) |
10
+ | setNotification | `setNotification(message)` | `setNotification(message, uniqueId)` (requires 2 args) |
11
+ | openFile | `openFile({ fileName, ... })` | Must include `fileSize` property in FileDetails |
12
+ | SubmitMode | `Xrm.Attributes.SubmitMode` | `Xrm.SubmitMode` |
13
+ | const enum in .d.ts | `const enum` in `.d.ts` files | Use `const enum` in `.ts` ES modules (typegen 0.8.0+ generates `.ts` files, resolving this issue) |
14
+ | Grid.refresh() | `grid.refresh()` | `(grid as any).refresh()` (not typed in @types/xrm) |
@@ -0,0 +1,50 @@
1
+ # @xrmforge/helpers Package
2
+
3
+ ### 13.1 Problem
4
+
5
+ The previous approach used a `/helpers` subpath export on `@xrmforge/typegen`. This was confusing because typegen is a Node.js code generation tool, while helpers are browser-safe runtime utilities. The subpath `@xrmforge/typegen/helpers` was non-obvious and AI coding assistants consistently failed to discover it.
6
+
7
+ ### 13.2 Solution
8
+
9
+ A standalone `@xrmforge/helpers` package consolidates all browser-safe runtime code. Zero Node.js dependencies. Clean, discoverable import path:
10
+
11
+ ```typescript
12
+ // Import from the dedicated helpers package
13
+ import { select, parseLookup, typedForm } from '@xrmforge/helpers';
14
+ ```
15
+
16
+ ### 13.3 Exports
17
+
18
+ **Web API Helpers:**
19
+ - `select(...fields: string[]): string` - Builds `?$select=field1,field2`
20
+ - `selectExpand(fields: string[], expand: string): string` - Builds `?$select=...&$expand=...`
21
+ - `parseLookup(response: Record<string, unknown>, fieldName: string): LookupValue | null` - Parses `_fieldname_value` with OData annotations
22
+ - `parseLookups(response: Record<string, unknown>, fieldName: string): LookupValue[]` - Multi-value lookup parsing
23
+ - `parseFormattedValue(response: Record<string, unknown>, fieldName: string): string | null` - Extracts `@OData.Community.Display.V1.FormattedValue`
24
+
25
+ **Xrm Constants (8 const enums):**
26
+ - DisplayState, FormNotificationLevel, RequiredLevel, SubmitMode, SaveMode, ClientType, ClientState, OperationType
27
+
28
+ **typedForm() Proxy:**
29
+ - `typedForm<TForm>(formContext)` - Returns a proxy where `form.name` delegates to `getAttribute('name')`
30
+ - GET trap: Property access delegates to getAttribute(); `$context` returns raw FormContext; `$control(name)` returns getControl()
31
+ - SET trap: Throws TypeError forcing `.setValue()` usage
32
+ - HAS trap: Checks if attribute exists on the form
33
+
34
+ **Action/Function Runtime:**
35
+ - `createBoundAction(entityName, actionName)` - Creates a bound action executor
36
+ - `executeRequest(request)` - Executes an Organization Request via Xrm.WebApi.online.execute
37
+ - `withProgress(message, fn)` - Wraps an async operation with Xrm.Utility.showProgressIndicator
38
+
39
+ ### 13.4 Migration
40
+
41
+ The old import path `@xrmforge/typegen/helpers` has been removed. Update all imports:
42
+
43
+ ```typescript
44
+ // Old (removed)
45
+ import { select } from '@xrmforge/typegen/helpers';
46
+ import { typedForm } from '@xrmforge/formhelpers';
47
+
48
+ // New
49
+ import { select, typedForm } from '@xrmforge/helpers';
50
+ ```
@@ -0,0 +1,21 @@
1
+ # Showcases
2
+
3
+ ### 14.1 Markant WebResources (Production Showcase)
4
+
5
+ Located in the XrmForge-Workspace repository under `docs/07_showcase/markant-webresources/`.
6
+
7
+ - **30 WebResources** in `src/forms/` (account, contact, opportunity, lead, quote, email, task, etc.)
8
+ - **1 shared library** (GDPR retention UI)
9
+ - **9 test files** with 59 tests
10
+ - **79 generated typings:** 25 form interfaces, 28 entity interfaces, 22 OptionSet files, 4 action executors
11
+ - **esbuild build** via xrmforge.config.json (32 entries)
12
+ - **Deploy script** (deploy.mjs) with @azure/identity auth, incremental deployment, hash-based change detection
13
+ - **27 entities, 236 OptionSet enums, 95 form interfaces, 7 Custom API executors**
14
+
15
+ ### 14.2 LMApp WebResources (KI Comparison Showcase)
16
+
17
+ Created during the KI comparison tests (Session 9). 18 legacy JavaScript form scripts (~8,400 lines) converted to TypeScript with XrmForge patterns.
18
+
19
+ - **19 WebResources** with Fields Enums, EntityNames, OptionSet Enums
20
+ - **84 tests** in 8 test files
21
+ - **XrmForge-optimized:** All 10 AGENT.md rules applied (FormContext cast, Fields Enum, EntityNames, OptionSet Enums, shared getLookupObject, Tab Enums)
@@ -0,0 +1,49 @@
1
+ # CI/CD
2
+
3
+ ### 15.1 GitHub Actions CI (`.github/workflows/ci.yml`)
4
+
5
+ **Triggers:** Push to main, Pull Requests against main.
6
+
7
+ **Matrix:** Node 20, Node 22 on ubuntu-latest.
8
+
9
+ **Steps:**
10
+ 1. Checkout
11
+ 2. Setup pnpm (from packageManager field)
12
+ 3. Setup Node.js (matrix version)
13
+ 4. `pnpm install --frozen-lockfile`
14
+ 5. `pnpm lint`
15
+ 6. `pnpm -r exec tsc --noEmit` (typecheck all packages)
16
+ 7. `pnpm build`
17
+ 8. `pnpm test`
18
+ 9. Coverage (Node 22 only): `npx vitest run --coverage` in typegen
19
+
20
+ ### 15.2 Release Workflow (`.github/workflows/release.yml`)
21
+
22
+ **Triggers:** After successful CI on push to main.
23
+
24
+ **Steps:**
25
+ 1. Checkout, setup pnpm, setup Node 22
26
+ 2. `pnpm install --frozen-lockfile`
27
+ 3. `pnpm build`
28
+ 4. Changesets action: creates Release PR or publishes to npm
29
+
30
+ **Publish command:** `pnpm release` = `turbo run build && changeset publish`
31
+
32
+ ### 15.3 Turbo Pipeline
33
+
34
+ ```
35
+ build: dependsOn: [^build], outputs: [dist/**]
36
+ test: dependsOn: [build]
37
+ typecheck: dependsOn: [^build]
38
+ lint: (no dependencies)
39
+ dev: cache: false, persistent: true
40
+ clean: cache: false
41
+ ```
42
+
43
+ ### 15.4 Changesets
44
+
45
+ Configured for public npm access, auto-update internal dependencies on patch level. Publish requires NPM_TOKEN secret.
46
+
47
+ ### 15.5 Publishing Order
48
+
49
+ Due to internal dependencies: typegen first, then devkit, then cli. Must use `pnpm publish` (not `npm publish`) to resolve `workspace:*` references to real versions.
@@ -0,0 +1,17 @@
1
+ # Technical Debt
2
+
3
+ ### 16.1 Known Issues
4
+
5
+ | Issue | Status | Priority |
6
+ |-------|--------|----------|
7
+ | parseLookup/select not adopted by AI assistants | Open | High |
8
+ | release.yml double runs (CI triggers release, release re-triggers CI) | Open | Low |
9
+ | No integration tests against live Dataverse | Open (OE-4) | Medium |
10
+ | @xrmforge/webapi has no Action/Function support | Accepted | Low |
11
+ | devDependency versions in scaffolded package.json are pinned to old versions | Open | Low |
12
+
13
+ ### 16.2 Accepted Limitations
14
+
15
+ - **const enum limitation:** Resolved in typegen 0.8.0. Generated output is now `.ts` ES modules, so `const enum` works directly with vitest and other test frameworks.
16
+ - **Grid.refresh() requires `as any`:** Not typed in @types/xrm.
17
+ - **Single solution per entity:** If an entity appears in multiple solutions, it is only generated once.
@@ -0,0 +1,25 @@
1
+ # Roadmap
2
+
3
+ ### 17.1 Next Steps (Priority Order)
4
+
5
+ 1. **parseLookup/select Adoption** - Improve AGENT.md examples so AI assistants consistently use `/helpers` imports
6
+ 2. **LMApp Showcase regeneration** - With latest releases (testing@0.2.0, devkit@0.4.0 with improved AGENT.md)
7
+ 3. **KI Battle Round 3** - Re-test Sonnet vs Opus after improvements to measure progress
8
+ 4. **Documentation website** - xrmforge.dev or xrmforge.io (OE-3)
9
+
10
+ ### 17.2 Open Decisions
11
+
12
+ | ID | Decision | Status |
13
+ |----|----------|--------|
14
+ | OE-1 | npm scope availability (@xrmforge) | Open |
15
+ | OE-2 | GitHub org vs personal repo | Decided: personal (juergenbeck/XrmForge) |
16
+ | OE-3 | Documentation domain (xrmforge.dev or .io) | Open |
17
+ | OE-4 | Dataverse test environment for integration tests | Open |
18
+ | OE-5 | Publisher prefix and solution name for PCF/WebResource tests | Open |
19
+
20
+ ### 17.3 Future Possibilities
21
+
22
+ - Relationship Names const enum (OE-7, low priority)
23
+ - @xrmforge/webapi with Action/Function support (reuse DataverseHttpClient)
24
+ - Plugin system for custom generators and type mappings
25
+ - Server-side generation (Custom API in Dataverse)
@@ -0,0 +1,22 @@
1
+ # Design Principles
2
+
3
+ The 18 design principles that govern all XrmForge development:
4
+
5
+ 1. **Extend, don't replace** - Types build on @types/xrm, never override them.
6
+ 2. **TypeScript all the way** - 100% TypeScript-native. No .NET, no ADAL.
7
+ 3. **Code must build** - Every work step ends with green build + tests.
8
+ 4. **Research before speed** - Investigate, compare, decide, then implement. Never guess.
9
+ 5. **No module without basics** - Error handling, logging, unit tests, JSDoc on all public APIs.
10
+ 6. **Monorepo discipline** - Each package standalone, no circular deps, barrel exports.
11
+ 7. **Enterprise resilience** - Retry + exponential backoff, rate-limit awareness, token caching, read-only default.
12
+ 8. **esbuild-first, webpack-compatible** - Default: esbuild (fast). webpack stays supported. IIFE output for D365.
13
+ 9. **MSAL-only authentication** - Only @azure/identity (no legacy ADAL). Three flows: client credentials, browser, device code.
14
+ 10. **Review required** - After every step, immediate critical review (6 dimensions). No asking if review is wanted.
15
+ 11. **Session state required** - session-state.md updated, changelog written, open questions tracked.
16
+ 12. **No half measures** - Every step completed fully: green build + tests + review before next step.
17
+ 13. **Informed architecture decisions** - Research, compare, recommend with pros/cons, get decision, persist.
18
+ 14. **Abstraction over vendor lock-in** - External dependencies behind interfaces (parser, auth, bundler).
19
+ 15. **Dual-language labels** - Primary language (1033/English) for identifiers, secondary in JSDoc. German umlauts transliterated.
20
+ 16. **Review with research and live verification** - Internet research, live D365 verification, production code checks, cite sources.
21
+ 17. **Challenge postponement** - "Later" check: Will it get harder? API contract? Real effort? Technical reasons?
22
+ 18. **Read-only default for Dataverse access** - DataverseHttpClient defaults to readOnly: true. Write access is an explicit opt-in.
@@ -0,0 +1,26 @@
1
+ # XrmForge Architektur
2
+
3
+ > **Status:** Lebendes Dokument, das den aktuellen Implementierungsstand beschreibt.
4
+ > **Letztes Update:** 2026-04-04 (Session 10)
5
+ > **Version:** 7 Packages, 666+ Tests über alle Packages.
6
+
7
+ ## Kapitel
8
+
9
+ 1. [Zusammenfassung](01-zusammenfassung.md)
10
+ 2. [Package-Architektur](02-packages.md)
11
+ 3. [Generierte Typen](03-generierte-typen.md)
12
+ 4. [CLI-Befehle](04-cli.md)
13
+ 5. [Build-Architektur](05-build.md)
14
+ 6. [Inkrementelle Generierung](06-inkrementell.md)
15
+ 7. [HTTP-Client](07-http-client.md)
16
+ 8. [Authentifizierung](08-authentifizierung.md)
17
+ 9. [Test-Framework](09-testing.md)
18
+ 10. [ESLint-Plugin](10-eslint-plugin.md)
19
+ 11. [AGENT.md-System](11-agent-md.md)
20
+ 12. [@types/xrm-Fallstricke](12-xrm-fallstricke.md)
21
+ 13. [@xrmforge/helpers Package](13-helpers.md)
22
+ 14. [Showcases](14-showcases.md)
23
+ 15. [CI/CD](15-ci-cd.md)
24
+ 16. [Technische Schulden](16-technische-schulden.md)
25
+ 17. [Roadmap](17-roadmap.md)
26
+ 18. [Designprinzipien](18-designprinzipien.md)
@@ -0,0 +1,11 @@
1
+ # 1. Zusammenfassung
2
+
3
+ XrmForge ist ein quelloffenes TypeScript-Toolkit für typsichere Dynamics 365 / Dataverse WebResource-Entwicklung. Es generiert TypeScript-Deklarationen aus Live-Dataverse-Metadaten und verwandelt Laufzeit-String-Fehler in Kompilierzeit-Typfehler.
4
+
5
+ **Kernnutzenversprechen:** Jeder Feldname, OptionSet-Wert, Tab-Name, Entitätsname und Subgrid-Name wird zu einer typisierten Konstante mit IDE-Autovervollständigung und Kompilierzeit-Validierung.
6
+
7
+ **Zielgruppe:** D365-Entwickler, die Formularskripte (WebResources) in JavaScript/TypeScript schreiben und Kompilierzeit-Sicherheit, null Magic Strings und moderne Werkzeuge (esbuild, vitest, ESLint) wollen.
8
+
9
+ **Technologie-Stack:** TypeScript, pnpm-Monorepo mit Turborepo, esbuild für IIFE-Bundles, vitest für Tests, @azure/identity für Authentifizierung, fast-xml-parser für FormXml-Parsing.
10
+
11
+ **npm-Organisation:** [@xrmforge](https://www.npmjs.com/org/xrmforge)
@@ -0,0 +1,110 @@
1
+ # 2. Package-Architektur
2
+
3
+ ## 2.1 Package-Übersicht
4
+
5
+ | Package | Version | Tests | Beschreibung |
6
+ |---------|---------|-------|--------------|
7
+ | @xrmforge/typegen | 0.8.0 | 444 | Kern: Typgenerierungs-Engine, Metadaten-Client, HTTP-Client, Hilfsfunktionen |
8
+ | @xrmforge/cli | 0.4.2 | 10 | CLI: generate-, build-, init-Befehle |
9
+ | @xrmforge/testing | 0.2.0 | 76 | Test-Hilfsmittel: createFormMock, fireOnChange, setupXrmMock |
10
+ | @xrmforge/helpers | 0.1.0 | 59 | Browsersichere Laufzeit: select(), parseLookup(), typedForm(), Xrm-Konstanten, Action-Executors |
11
+ | @xrmforge/webapi | 0.1.0 | 45 | Typsicherer Xrm.WebApi-Client mit QueryBuilder |
12
+ | @xrmforge/devkit | 0.4.0 | 42 | Build-Orchestrierung, Scaffolding, AGENT.md-Generierung |
13
+ | @xrmforge/eslint-plugin | 0.2.0 | 32 | 5 D365-spezifische ESLint-Regeln |
14
+
15
+ **Gesamt:** 708 Tests über 7 Packages.
16
+
17
+ ## 2.2 Abhängigkeitsgraph
18
+
19
+ ```
20
+ @xrmforge/cli
21
+ |-- @xrmforge/typegen (generate-Befehl)
22
+ |-- @xrmforge/devkit (build- + init-Befehle)
23
+ '-- commander (CLI-Framework)
24
+
25
+ @xrmforge/typegen
26
+ |-- @azure/identity (Authentifizierung)
27
+ '-- fast-xml-parser (FormXml-Parsing)
28
+
29
+ @xrmforge/devkit
30
+ '-- esbuild (IIFE-Bundling)
31
+
32
+ @xrmforge/testing (keine Laufzeit-Abhängigkeiten)
33
+ @xrmforge/helpers (keine Laufzeit-Abhängigkeiten)
34
+ @xrmforge/webapi (keine Laufzeit-Abhängigkeiten)
35
+ @xrmforge/eslint-plugin (ESLint Peer-Abhängigkeit)
36
+ ```
37
+
38
+ ## 2.3 Package-Details
39
+
40
+ ### @xrmforge/typegen
41
+
42
+ Das Kern-Package. Enthält:
43
+
44
+ - **TypeGenerationOrchestrator** - Koordiniert die gesamte Generierungs-Pipeline
45
+ - **MetadataClient** - Fragt Dataverse-Metadaten ab (Entitäten, Formulare, OptionSets, Custom APIs)
46
+ - **DataverseHttpClient** - Belastbarer REST-Client mit Retry, Rate Limiting, Nebenläufigkeitssteuerung
47
+ - **ChangeDetector** - Inkrementelle Generierung über RetrieveMetadataChanges
48
+ - **MetadataCache** - Dateisystem-basiertes Caching mit Versionsstempeln
49
+ - **Generators** - Entitäts-Interfaces, Formular-Interfaces, OptionSet-Enums, Fields-Enums, EntityNames, Navigations-Properties, Action/Function-Executors
50
+ - **Helpers** - select(), parseLookup(), parseFormattedValue() (verschoben nach @xrmforge/helpers)
51
+ - **Xrm-Konstanten** - DisplayState, FormNotificationLevel, RequiredLevel, SubmitMode, SaveMode, ClientType, ClientState (verschoben nach @xrmforge/helpers)
52
+ - **Authentifizierung** - createCredential()-Factory für 4 Authentifizierungsmethoden
53
+ - **Logging** - Scope-basierte Logger mit austauschbaren Senken (Console, JSON, Silent)
54
+ - **Fehler** - Strukturierte Fehlerhierarchie mit ErrorCode-Enum (AUTH_1xxx, API_2xxx, META_3xxx, GEN_4xxx, CONFIG_5xxx)
55
+
56
+ ### @xrmforge/cli
57
+
58
+ Kommandozeilen-Interface basierend auf commander.js. Drei Befehle:
59
+ - `xrmforge generate` - Orchestriert den TypeGenerationOrchestrator
60
+ - `xrmforge build` - Delegiert an devkit build()
61
+ - `xrmforge init` - Delegiert an devkit scaffoldProject()
62
+
63
+ ### @xrmforge/testing
64
+
65
+ FormContext-Mocking für Unit-Tests:
66
+ - `createFormMock<TForm>(values)` - Erstellt einen vollständigen Mock aus einfachen Schlüssel-Wert-Paaren
67
+ - `MockAttribute` - getValue/setValue, Dirty-Tracking, onChange-Handler, Required Level, Submit Mode
68
+ - `MockControl` - Sichtbar, Deaktiviert, Label, Benachrichtigungen
69
+ - `MockUi` - Formular-Benachrichtigungen, Tab/Section-Stubs
70
+ - `MockEntity` - Entitäts-ID, Name, Primärattribut
71
+ - `fireOnChange(fieldName)` - Löst registrierte onChange-Handler aus
72
+ - `setupXrmMock(options)` / `teardownXrmMock()` - Globaler Xrm-Mock mit WebApi/Navigation-Stubs
73
+
74
+ ### @xrmforge/helpers
75
+
76
+ Bündelt allen browsersicheren Laufzeitcode. Keine Node.js-Abhängigkeiten. Enthält:
77
+ - **Web-API-Helpers** - select(), parseLookup(), parseFormattedValue()
78
+ - **Xrm-Konstanten** - DisplayState, SubmitMode, RequiredLevel, SaveMode, ClientType, ClientState, FormNotificationLevel, OperationType
79
+ - **Action/Function-Executors** - createBoundAction(), executeRequest(), withProgress()
80
+ - **typedForm()-Proxy** - Proxy-basierter FormContext-Wrapper, bei dem `form.name` an `getAttribute('name')` delegiert
81
+
82
+ ### @xrmforge/webapi
83
+
84
+ Typsicherer Wrapper um Xrm.WebApi:
85
+ - `retrieve<T>(entityName, id, query)` - Einzelner Datensatz
86
+ - `retrieveMultiple<T>(entityName, query, options)` - Mit Paginierung (maxPages)
87
+ - `create(entityName, data)` - Gibt Datensatz-ID zurück
88
+ - `update(entityName, id, data)` - Void
89
+ - `remove(entityName, id)` - Void
90
+ - `QueryBuilder` - Fluent API: `.select().filter().orderBy().top().expand().build()`
91
+ - `WebApiError` - Strukturierte Fehler mit statusCode, errorCode, innerMessage
92
+
93
+ ### @xrmforge/devkit
94
+
95
+ Build-Orchestrierung und Projekt-Scaffolding:
96
+ - `build(config)` - Parallele esbuild-IIFE-Builds über Promise.allSettled
97
+ - `watch(config)` - esbuild-Watch-Modus mit Rebuild-Callbacks
98
+ - `scaffoldProject(config)` - Generiert 11 Projektdateien aus Vorlagen
99
+ - `validateBuildConfig(config)` / `resolveBuildConfig(config)` - Konfigurationsvalidierung
100
+ - `BuildError` mit Codes: CONFIG_INVALID, ENTRY_NOT_FOUND, BUILD_FAILED, WATCH_ERROR
101
+ - Vorlagensystem: 7 Textvorlagen in `src/scaffold/templates/`, geladen über `template-loader.ts`
102
+
103
+ ### @xrmforge/eslint-plugin
104
+
105
+ 5 Regeln für D365-Formularskripte (ESLint v9 Flat Config):
106
+ - `no-xrm-page` (error) - Verbietet die veraltete Xrm.Page-API
107
+ - `no-magic-optionset` (warn) - Verbietet Magic Numbers in OptionSet-Vergleichen
108
+ - `no-sync-webapi` (error) - Verbietet synchrone XMLHttpRequest
109
+ - `require-error-handling` (warn) - Verlangt try/catch in asynchronen on*-Event-Handlern
110
+ - `require-namespace` (warn) - Verbietet window/globalThis-Zuweisungen
@@ -0,0 +1,172 @@
1
+ # 3. Generierte Typen
2
+
3
+ Die Ausführung von `xrmforge generate` erzeugt die folgenden TypeScript-ES-Module:
4
+
5
+ ## 3.1 Entitäts-Interfaces (`entities/{entity}.ts`)
6
+
7
+ ```typescript
8
+ // generated/entities/account.ts
9
+ /** Account | Konto */
10
+ export interface Account {
11
+ /** Account Name | Kontoname */
12
+ name: string | null;
13
+ accountid: string | null;
14
+ revenue: number | null;
15
+ _parentaccountid_value: string | null; // Lookup GUID
16
+ // ...
17
+ }
18
+ ```
19
+
20
+ **Typ-Zuordnung:** String/Memo/EntityName zu `string`, Integer/BigInt/Decimal/Double/Money zu `number`, Boolean zu `boolean`, DateTime/Uniqueidentifier/Lookup zu `string`, Picklist/State/Status zu `number`.
21
+
22
+ ## 3.2 Entity Fields Enums (`fields/{entity}.ts`)
23
+
24
+ ```typescript
25
+ // generated/fields/account.ts
26
+ export const enum AccountFields {
27
+ /** Account Name | Kontoname */
28
+ Name = 'name',
29
+ Telephone1 = 'telephone1',
30
+ Revenue = 'revenue',
31
+ // alle Entitätsattribute für $select-Abfragen
32
+ }
33
+
34
+ export const enum AccountNavigationProperties {
35
+ PrimaryContact = 'primarycontactid',
36
+ ContactCustomerAccounts = 'contact_customer_accounts',
37
+ // alle Lookup-Navigations-Properties
38
+ }
39
+ ```
40
+
41
+ Verwendet für Web API `$select`: `select(AccountFields.Name, AccountFields.Revenue)`.
42
+
43
+ ## 3.3 Navigations-Properties (`fields/{entity}.ts`)
44
+
45
+ Navigations-Property-Enums befinden sich zusammen mit den Fields-Enums in derselben Datei (siehe 3.2 oben). Beispielverwendung:
46
+
47
+ ```typescript
48
+ import { AccountNavigationProperties } from '../generated/fields/account';
49
+ // verwendet für $expand-Abfragen
50
+ ```
51
+
52
+ ## 3.4 Formular-Interfaces (`forms/{entity}.ts`)
53
+
54
+ ```typescript
55
+ // generated/forms/account.ts
56
+
57
+ // Union-Typ, der gültige Feldnamen einschränkt
58
+ export type AccountMainFormFields = 'name' | 'telephone1' | 'revenue';
59
+
60
+ // Gemappter Typ: Feldname zu Xrm-Attributtyp
61
+ export type AccountMainFormAttributeMap = {
62
+ name: Xrm.Attributes.StringAttribute;
63
+ telephone1: Xrm.Attributes.StringAttribute;
64
+ revenue: Xrm.Attributes.NumberAttribute;
65
+ };
66
+
67
+ // Gemappter Typ: Feldname zu Xrm-Steuerelementtyp
68
+ export type AccountMainFormControlMap = {
69
+ name: Xrm.Controls.StringControl;
70
+ telephone1: Xrm.Controls.StringControl;
71
+ revenue: Xrm.Controls.NumberControl;
72
+ };
73
+
74
+ // Fields-Enum für Autovervollständigung
75
+ export const enum AccountMainFormFieldsEnum {
76
+ /** Account Name | Kontoname */
77
+ AccountName = 'name',
78
+ Telephone1 = 'telephone1',
79
+ Revenue = 'revenue',
80
+ }
81
+
82
+ // Typsicherer FormContext mit überladenen getAttribute/getControl
83
+ export interface AccountMainForm extends Omit<Xrm.FormContext, 'getAttribute' | 'getControl'> {
84
+ getAttribute<K extends AccountMainFormFields>(name: K): AccountMainFormAttributeMap[K];
85
+ getAttribute(index: number): Xrm.Attributes.Attribute;
86
+ getAttribute(): Xrm.Attributes.Attribute[];
87
+
88
+ getControl<K extends AccountMainFormFields>(name: K): AccountMainFormControlMap[K];
89
+ getControl(index: number): Xrm.Controls.Control;
90
+ getControl(): Xrm.Controls.Control[];
91
+ }
92
+ ```
93
+
94
+ **Spezielle Steuerelemente** werden anhand ihrer FormXml-ClassID typisiert:
95
+ - Subgrid: `Xrm.Controls.GridControl`
96
+ - Editierbares Grid: `Xrm.Controls.GridControl`
97
+ - Quick View: `Xrm.Controls.QuickFormControl`
98
+ - Web Resource / iFrame: `Xrm.Controls.IframeControl`
99
+
100
+ ## 3.5 Tabs/Sections/Subgrids/QuickViews Enums
101
+
102
+ ```typescript
103
+ const enum AccountMainFormTabs { Summary = 'SUMMARY_TAB', Details = 'DETAILS_TAB' }
104
+ const enum AccountMainFormSections { General = 'GENERAL', Address = 'ADDRESS' }
105
+ const enum AccountMainFormSubgrids { Contacts = 'Contacts_Subgrid' }
106
+ const enum AccountMainFormQuickViews { ContactPreview = 'ContactQuickView' }
107
+ ```
108
+
109
+ ## 3.6 OptionSet Enums (`optionsets/{entity}.ts`)
110
+
111
+ ```typescript
112
+ // generated/optionsets/account.ts
113
+ /** Account Category Code | Kontokategoriecode */
114
+ export const enum AccountCategoryCode {
115
+ /** Preferred Customer | Bevorzugter Kunde */
116
+ PreferredCustomer = 1,
117
+ Standard = 2,
118
+ }
119
+ ```
120
+
121
+ Umfasst Picklist-, Status-, State- und MultiSelectPicklist-Attribute. Doppelte Labels werden mit dem Suffix `_{Value}` disambiguiert.
122
+
123
+ ## 3.7 EntityNames Enum (`entity-names.ts`)
124
+
125
+ ```typescript
126
+ // generated/entity-names.ts
127
+ export const enum EntityNames {
128
+ Account = 'account',
129
+ Contact = 'contact',
130
+ // alle Entitäten im Scope
131
+ }
132
+ ```
133
+
134
+ ## 3.8 MockValues-Typen (in Formular-Interfaces)
135
+
136
+ ```typescript
137
+ type AccountMainFormMockValues = {
138
+ name?: string | null;
139
+ telephone1?: string | null;
140
+ revenue?: number | null;
141
+ };
142
+ ```
143
+
144
+ Verwendet mit `createFormMock<AccountMainForm, AccountMainFormMockValues>({ name: 'Test' })`.
145
+
146
+ ## 3.9 Action/Function Executors (`actions/{entity|global}.ts`)
147
+
148
+ ```typescript
149
+ // generated/actions/global.ts
150
+ import { createUnboundAction } from '@xrmforge/helpers';
151
+
152
+ export interface NormalizePhoneParams { Input: string; AllowSuspicious?: boolean; }
153
+ export interface NormalizePhoneResult { Normalized: string; Status: number; }
154
+
155
+ export const NormalizePhone = createUnboundAction<NormalizePhoneParams, NormalizePhoneResult>(
156
+ 'markant_NormalizePhone',
157
+ { Input: { typeName: 'String', structuralProperty: 1 } }
158
+ );
159
+ // Verwendung: const result = await NormalizePhone.execute({ Input: '123' });
160
+ ```
161
+
162
+ Factory-Funktionen: `createBoundAction`, `createUnboundAction`, `createBoundFunction`, `createUnboundFunction`. Batch-Ausführung über `executeMultiple()`, Fortschritts-UI über `withProgress()`.
163
+
164
+ ## 3.10 Zweisprachige Labels
165
+
166
+ Alle generierten JSDoc-Kommentare unterstützen zweisprachige Labels:
167
+ ```typescript
168
+ /** Account Name | Kontoname */
169
+ Name = 'name',
170
+ ```
171
+
172
+ Deutsche Umlaute werden in Bezeichnern transliteriert: ae, oe, ue, ss (z.B. "Übergeordnet" wird zu `Uebergeordnet`).