@xrmforge/typegen 0.6.0 → 0.7.1

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 (44) hide show
  1. package/MIGRATION.md +14 -3
  2. package/dist/index.d.ts +203 -2
  3. package/docs/architecture/00-README.md +26 -0
  4. package/docs/architecture/01-executive-summary.md +11 -0
  5. package/docs/architecture/02-packages.md +110 -0
  6. package/docs/architecture/03-generated-types.md +176 -0
  7. package/docs/architecture/04-cli.md +58 -0
  8. package/docs/architecture/05-build.md +50 -0
  9. package/docs/architecture/06-incremental.md +42 -0
  10. package/docs/architecture/07-http-client.md +59 -0
  11. package/docs/architecture/08-authentication.md +18 -0
  12. package/docs/architecture/09-testing.md +55 -0
  13. package/docs/architecture/10-eslint-plugin.md +82 -0
  14. package/docs/architecture/11-agent-md.md +38 -0
  15. package/docs/architecture/12-xrm-pitfalls.md +14 -0
  16. package/docs/architecture/13-helpers.md +50 -0
  17. package/docs/architecture/14-showcases.md +21 -0
  18. package/docs/architecture/15-ci-cd.md +49 -0
  19. package/docs/architecture/16-technical-debt.md +17 -0
  20. package/docs/architecture/17-roadmap.md +25 -0
  21. package/docs/architecture/18-design-principles.md +22 -0
  22. package/docs/architektur/00-README.md +26 -0
  23. package/docs/architektur/01-zusammenfassung.md +11 -0
  24. package/docs/architektur/02-packages.md +110 -0
  25. package/docs/architektur/03-generierte-typen.md +176 -0
  26. package/docs/architektur/04-cli.md +58 -0
  27. package/docs/architektur/05-build.md +50 -0
  28. package/docs/architektur/06-inkrementell.md +42 -0
  29. package/docs/architektur/07-http-client.md +59 -0
  30. package/docs/architektur/08-authentifizierung.md +18 -0
  31. package/docs/architektur/09-testing.md +55 -0
  32. package/docs/architektur/10-eslint-plugin.md +82 -0
  33. package/docs/architektur/11-agent-md.md +38 -0
  34. package/docs/architektur/12-xrm-fallstricke.md +14 -0
  35. package/docs/architektur/13-helpers.md +50 -0
  36. package/docs/architektur/14-showcases.md +21 -0
  37. package/docs/architektur/15-ci-cd.md +49 -0
  38. package/docs/architektur/16-technische-schulden.md +17 -0
  39. package/docs/architektur/17-roadmap.md +25 -0
  40. package/docs/architektur/18-designprinzipien.md +22 -0
  41. package/package.json +4 -8
  42. package/dist/helpers.d.ts +0 -203
  43. package/dist/helpers.js +0 -120
  44. package/dist/helpers.js.map +0 -1
@@ -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.6.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,176 @@
1
+ # 3. Generierte Typen
2
+
3
+ Die Ausführung von `xrmforge generate` erzeugt die folgenden TypeScript-Deklarationen:
4
+
5
+ ## 3.1 Entitäts-Interfaces (`entities/{entity}.d.ts`)
6
+
7
+ ```typescript
8
+ declare namespace XrmForge.Entities {
9
+ /** Account | Konto */
10
+ 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
+
21
+ **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`.
22
+
23
+ ## 3.2 Entity Fields Enums (`entities/{entity}.d.ts`)
24
+
25
+ ```typescript
26
+ declare namespace XrmForge.Entities {
27
+ const enum AccountFields {
28
+ /** Account Name | Kontoname */
29
+ Name = 'name',
30
+ Revenue = 'revenue',
31
+ // alle lesbaren Attribute
32
+ }
33
+ }
34
+ ```
35
+
36
+ Verwendet für Web API `$select`: `select(AccountFields.Name, AccountFields.Revenue)`.
37
+
38
+ ## 3.3 Navigations-Properties (`entities/{entity}.d.ts`)
39
+
40
+ ```typescript
41
+ declare namespace XrmForge.Entities {
42
+ const enum AccountNavigation {
43
+ PrimaryContactId = 'primarycontactid',
44
+ ContactCustomerAccounts = 'contact_customer_accounts',
45
+ // OneToMany- + ManyToMany-Beziehungen
46
+ }
47
+ }
48
+ ```
49
+
50
+ ## 3.4 Formular-Interfaces (`forms/{entity}.d.ts`)
51
+
52
+ ```typescript
53
+ declare namespace XrmForge.Forms.Account {
54
+ // Union-Typ, der gültige Feldnamen einschränkt
55
+ type AccountMainFormFields = 'name' | 'telephone1' | 'revenue';
56
+
57
+ // Gemappter Typ: Feldname zu Xrm-Attributtyp
58
+ type AccountMainFormAttributeMap = {
59
+ name: Xrm.Attributes.StringAttribute;
60
+ telephone1: Xrm.Attributes.StringAttribute;
61
+ revenue: Xrm.Attributes.NumberAttribute;
62
+ };
63
+
64
+ // Gemappter Typ: Feldname zu Xrm-Steuerelementtyp
65
+ type AccountMainFormControlMap = {
66
+ name: Xrm.Controls.StringControl;
67
+ telephone1: Xrm.Controls.StringControl;
68
+ revenue: Xrm.Controls.NumberControl;
69
+ };
70
+
71
+ // Fields-Enum für Autovervollständigung
72
+ const enum AccountMainFormFieldsEnum {
73
+ /** Account Name | Kontoname */
74
+ AccountName = 'name',
75
+ Telephone1 = 'telephone1',
76
+ Revenue = 'revenue',
77
+ }
78
+
79
+ // Typsicherer FormContext mit überladenen getAttribute/getControl
80
+ interface AccountMainForm extends Omit<Xrm.FormContext, 'getAttribute' | 'getControl'> {
81
+ getAttribute<K extends AccountMainFormFields>(name: K): AccountMainFormAttributeMap[K];
82
+ getAttribute(index: number): Xrm.Attributes.Attribute;
83
+ getAttribute(): Xrm.Attributes.Attribute[];
84
+
85
+ getControl<K extends AccountMainFormFields>(name: K): AccountMainFormControlMap[K];
86
+ getControl(index: number): Xrm.Controls.Control;
87
+ getControl(): Xrm.Controls.Control[];
88
+ }
89
+ }
90
+ ```
91
+
92
+ **Spezielle Steuerelemente** werden anhand ihrer FormXml-ClassID typisiert:
93
+ - Subgrid: `Xrm.Controls.GridControl`
94
+ - Editierbares Grid: `Xrm.Controls.GridControl`
95
+ - Quick View: `Xrm.Controls.QuickFormControl`
96
+ - Web Resource / iFrame: `Xrm.Controls.IframeControl`
97
+
98
+ ## 3.5 Tabs/Sections/Subgrids/QuickViews Enums
99
+
100
+ ```typescript
101
+ const enum AccountMainFormTabs { Summary = 'SUMMARY_TAB', Details = 'DETAILS_TAB' }
102
+ const enum AccountMainFormSections { General = 'GENERAL', Address = 'ADDRESS' }
103
+ const enum AccountMainFormSubgrids { Contacts = 'Contacts_Subgrid' }
104
+ const enum AccountMainFormQuickViews { ContactPreview = 'ContactQuickView' }
105
+ ```
106
+
107
+ ## 3.6 OptionSet Enums (`optionsets/{entity}.d.ts`)
108
+
109
+ ```typescript
110
+ declare namespace XrmForge.OptionSets.Account {
111
+ /** Account Category Code | Kontokategoriecode */
112
+ const enum AccountCategoryCode {
113
+ /** Preferred Customer | Bevorzugter Kunde */
114
+ PreferredCustomer = 1,
115
+ Standard = 2,
116
+ }
117
+ }
118
+ ```
119
+
120
+ Umfasst Picklist-, Status-, State- und MultiSelectPicklist-Attribute. Doppelte Labels werden mit dem Suffix `_{Value}` disambiguiert.
121
+
122
+ ## 3.7 EntityNames Enum (`entity-names.d.ts`)
123
+
124
+ ```typescript
125
+ declare namespace XrmForge {
126
+ const enum EntityNames {
127
+ Account = 'account',
128
+ Contact = 'contact',
129
+ // alle Entitäten im Scope
130
+ }
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}.d.ts` + `.ts`)
147
+
148
+ **Deklaration (.d.ts):**
149
+ ```typescript
150
+ declare namespace XrmForge.Actions {
151
+ interface NormalizePhoneParams { Input: string; AllowSuspicious?: boolean; }
152
+ interface NormalizePhoneResult { Normalized: string; Status: number; }
153
+ }
154
+ ```
155
+
156
+ **Laufzeitmodul (.ts):**
157
+ ```typescript
158
+ import { createUnboundAction } from '@xrmforge/typegen';
159
+ export const NormalizePhone = createUnboundAction<NormalizePhoneParams, NormalizePhoneResult>(
160
+ 'markant_NormalizePhone',
161
+ { Input: { typeName: 'String', structuralProperty: 1 } }
162
+ );
163
+ // Verwendung: const result = await NormalizePhone.execute({ Input: '123' });
164
+ ```
165
+
166
+ Factory-Funktionen: `createBoundAction`, `createUnboundAction`, `createBoundFunction`, `createUnboundFunction`. Batch-Ausführung über `executeMultiple()`, Fortschritts-UI über `withProgress()`.
167
+
168
+ ## 3.10 Zweisprachige Labels
169
+
170
+ Alle generierten JSDoc-Kommentare unterstützen zweisprachige Labels:
171
+ ```typescript
172
+ /** Account Name | Kontoname */
173
+ Name = 'name',
174
+ ```
175
+
176
+ Deutsche Umlaute werden in Bezeichnern transliteriert: ae, oe, ue, ss (z.B. "Übergeordnet" wird zu `Uebergeordnet`).
@@ -0,0 +1,58 @@
1
+ # 4. CLI-Befehle
2
+
3
+ ## 4.1 `xrmforge generate`
4
+
5
+ Generiert TypeScript-Deklarationen aus einer Dataverse-Umgebung.
6
+
7
+ | Flag | Typ | Standard | Beschreibung |
8
+ |------|-----|----------|--------------|
9
+ | `--url <url>` | string | erforderlich | Dataverse-Umgebungs-URL |
10
+ | `--auth <method>` | string | erforderlich | Authentifizierungsmethode: client-credentials, interactive, device-code, token |
11
+ | `--tenant-id <id>` | string | variiert | Azure AD Tenant-ID |
12
+ | `--client-id <id>` | string | variiert | Azure AD Application-ID |
13
+ | `--client-secret <s>` | string | variiert | Client Secret (nur client-credentials) |
14
+ | `--token <token>` | string | variiert | Vorab erworbenes Bearer-Token (nur Token-Auth) |
15
+ | `--entities <list>` | string | - | Kommagetrennte logische Entitätsnamen |
16
+ | `--solutions <list>` | string | - | Kommagetrennte eindeutige Lösungsnamen |
17
+ | `--output <dir>` | string | ./typings | Ausgabeverzeichnis |
18
+ | `--label-language <n>` | string | 1033 | Primäre Label-Sprache (LCID) |
19
+ | `--secondary-language <n>` | string | - | Sekundäre Label-Sprache für JSDoc |
20
+ | `--no-forms` | flag | - | Formular-Interface-Generierung überspringen |
21
+ | `--no-optionsets` | flag | - | OptionSet-Enum-Generierung überspringen |
22
+ | `--actions` | flag | false | Custom-API-Executors generieren |
23
+ | `--actions-filter <prefix>` | string | - | Custom APIs nach Uniquename-Präfix filtern |
24
+ | `--cache` | flag | false | Metadaten-Caching für inkrementelle Generierung aktivieren |
25
+ | `--no-cache` | flag | - | Vollständige Metadaten-Aktualisierung erzwingen |
26
+ | `--cache-dir <dir>` | string | .xrmforge/cache | Cache-Verzeichnis |
27
+ | `-v, --verbose` | flag | false | Debug-Logging |
28
+
29
+ Mindestens eines von `--entities` oder `--solutions` ist erforderlich.
30
+
31
+ ## 4.2 `xrmforge build`
32
+
33
+ Baut WebResources als IIFE-Bundles mit esbuild (über @xrmforge/devkit).
34
+
35
+ | Flag | Typ | Standard | Beschreibung |
36
+ |------|-----|----------|--------------|
37
+ | `--watch` | flag | false | Watch-Modus mit inkrementellen Rebuilds |
38
+ | `--minify` | flag | aus Konfiguration | Minifizierungseinstellung überschreiben |
39
+ | `--no-sourcemap` | flag | - | Source Maps deaktivieren |
40
+ | `--out-dir <dir>` | string | aus Konfiguration | Ausgabeverzeichnis überschreiben |
41
+ | `-v, --verbose` | flag | false | Fehlerstacks anzeigen |
42
+
43
+ Liest die Konfiguration aus `xrmforge.config.json`.
44
+
45
+ ## 4.3 `xrmforge init`
46
+
47
+ Erstellt ein neues D365-Formularskript-Projekt.
48
+
49
+ | Flag | Typ | Standard | Beschreibung |
50
+ |------|-----|----------|--------------|
51
+ | `[dir]` | positional | . | Zielverzeichnis |
52
+ | `--name <name>` | string | Verzeichnisname | Projektname für package.json |
53
+ | `--prefix <prefix>` | string | contoso | Publisher-Präfix |
54
+ | `--namespace <ns>` | string | PascalCase(prefix) | Basis-Namespace für Skripte |
55
+ | `--skip-install` | flag | false | npm install überspringen |
56
+ | `--force` | flag | false | Nicht-leere Verzeichnisse erlauben |
57
+
58
+ Generiert 11 Dateien: package.json, tsconfig.json, xrmforge.config.json, vitest.config.ts, .gitignore, AGENT.md, example-form.ts, example-form.test.ts, typings/.gitkeep, GitHub Actions CI, Azure DevOps Pipeline.
@@ -0,0 +1,50 @@
1
+ # 5. Build-Architektur
2
+
3
+ ## 5.1 esbuild IIFE-Bundles
4
+
5
+ XrmForge verwendet esbuild, um IIFE-Bundles (Immediately Invoked Function Expression) für Dynamics 365 zu erstellen. D365 erfordert, dass Skripte als namespace.function registriert werden (z.B. `Contoso.Account.onLoad`).
6
+
7
+ **esbuild-Konfiguration pro Eintrag:**
8
+ ```
9
+ format: 'iife'
10
+ bundle: true
11
+ globalName: entry.namespace // z.B. 'Contoso.Account'
12
+ target: config.target // Standard: 'es2020'
13
+ minify: config.minify
14
+ sourcemap: config.sourcemap
15
+ external: config.external // z.B. ['fs', 'path'] für Node.js-Abhängigkeiten
16
+ ```
17
+
18
+ Alle Einträge werden parallel mit `Promise.allSettled()` gebaut, was Teilerfolge ermöglicht.
19
+
20
+ ## 5.2 xrmforge.config.json Schema
21
+
22
+ ```json
23
+ {
24
+ "build": {
25
+ "outDir": "./dist/prefix_/JS",
26
+ "target": "es2020",
27
+ "sourcemap": true,
28
+ "minify": true,
29
+ "external": [],
30
+ "entries": {
31
+ "entry_name": {
32
+ "input": "./src/forms/account-form.ts",
33
+ "namespace": "Contoso.Account",
34
+ "out": "Account/OnLoad.js"
35
+ }
36
+ }
37
+ }
38
+ }
39
+ ```
40
+
41
+ ## 5.3 globalName-Behandlung
42
+
43
+ esbuild erstellt automatisch verschachtelte Globals aus gepunkteten Namespaces:
44
+ ```javascript
45
+ // namespace: "Contoso.Account" erzeugt:
46
+ var Contoso = Contoso || {};
47
+ Contoso.Account = (() => { return { onLoad, onSave }; })();
48
+ ```
49
+
50
+ D365-Event-Registrierung: `Contoso.Account.onLoad`.
@@ -0,0 +1,42 @@
1
+ # 6. Inkrementelle Generierung
2
+
3
+ ## 6.1 Übersicht
4
+
5
+ Die inkrementelle Generierung verwendet die Dataverse-Funktion `RetrieveMetadataChanges`, um zu erkennen, welche Entitäten sich seit der letzten Generierung geändert haben. Dies reduziert die Generierungszeit von Sekunden auf Millisekunden (gemessen: 4720ms auf 473ms, 10-fache Verbesserung).
6
+
7
+ ## 6.2 Komponenten
8
+
9
+ **ChangeDetector** (`src/metadata/change-detector.ts`):
10
+ - `getInitialVersionStamp()` - Erster Lauf: holt den initialen ServerVersionStamp
11
+ - `detectChanges(clientVersionStamp)` - Folgeläufe: gibt changedEntityNames, deletedEntityNames, newVersionStamp zurück
12
+
13
+ **MetadataCache** (`src/metadata/cache.ts`):
14
+ - Dateisystem-basiert: `.xrmforge/cache/metadata.json`
15
+ - Speichert: Manifest (Version, Umgebungs-URL, ServerVersionStamp, letzter Refresh, Entitätsliste) + entityTypeInfos pro Entität
16
+ - Validierung: prüft Cache-Version, Umgebungs-URL-Übereinstimmung, Dateiexistenz
17
+
18
+ ## 6.3 Ablauf
19
+
20
+ ```
21
+ Erster Lauf (kein Cache):
22
+ 1. Alle Entitäts-Metadaten abrufen
23
+ 2. getInitialVersionStamp()
24
+ 3. Cache mit ServerVersionStamp speichern
25
+
26
+ Folgelauf (Cache vorhanden):
27
+ 1. Cache laden, Umgebungs-URL validieren
28
+ 2. detectChanges(cachedVersionStamp)
29
+ 3. Nur geänderte Entitäten abrufen
30
+ 4. Gelöschte Entitäten aus Cache entfernen
31
+ 5. Cache mit neuem ServerVersionStamp speichern
32
+
33
+ Abgelaufener Stempel (>90 Tage):
34
+ Fehlercode 0x80044352 erkannt
35
+ Rückfall auf vollständigen Refresh
36
+ ```
37
+
38
+ ## 6.4 RetrieveMetadataChanges API
39
+
40
+ - **Typ:** OData-Funktion (GET, nicht POST)
41
+ - **URL:** `/RetrieveMetadataChanges(Query=@q,ClientVersionStamp=@s)?@q={...}&@s='...'`
42
+ - **Antwort:** EntityMetadata[] mit HasChanged-Flag, ServerVersionStamp, DeletedMetadata
@@ -0,0 +1,59 @@
1
+ # 7. HTTP-Client
2
+
3
+ ## 7.1 DataverseHttpClient
4
+
5
+ Der zentrale HTTP-Client (`src/http/client.ts`) bietet belastbare Kommunikation mit der Dataverse Web API.
6
+
7
+ **Kernmethoden:**
8
+ - `get<T>(path, signal?)` - Einzelne GET-Anfrage mit Retry
9
+ - `getAll<T>(path, signal?)` - GET mit automatischer @odata.nextLink-Paginierung (max 100 Seiten)
10
+
11
+ ## 7.2 Nur-Lesen-Standard
12
+
13
+ Der Client ist standardmäßig auf `readOnly: true` eingestellt und blockiert POST/PATCH/PUT/DELETE-Anfragen. Dies verhindert versehentliche Datenänderungen während der Typgenerierung. Schreibzugriff erfordert explizites `readOnly: false`.
14
+
15
+ ## 7.3 Retry mit exponentiellem Backoff
16
+
17
+ - **Basisverzögerung:** 1000ms (konfigurierbar)
18
+ - **Maximaler Backoff:** 60 Sekunden
19
+ - **Jitter:** Zufällige Verzögerung bis zur Basisverzögerung
20
+ - **Formel:** `min(baseDelay * 2^(attempt-1) + jitter, 60000)`
21
+ - **Maximale Retries:** konfigurierbar (Standard: 3)
22
+
23
+ ## 7.4 Rate Limiting (HTTP 429)
24
+
25
+ - **Separater Zähler** von Standard-Retries (nicht vermischt)
26
+ - **Retry-After-Header** wird respektiert (Sekunden in Millisekunden umgerechnet)
27
+ - **Maximal 10 aufeinanderfolgende 429-Retries** (DEFAULT_MAX_RATE_LIMIT_RETRIES)
28
+ - 429-Antworten inkrementieren den Standard-Retry-Zähler NICHT
29
+
30
+ ## 7.5 Nebenläufigkeitssteuerung
31
+
32
+ Nicht-rekursives Semaphor-Muster:
33
+ - **maxConcurrency:** 5 (Standard)
34
+ - Warteschlange mit FIFO-Reihenfolge
35
+ - Alle Retries erfolgen innerhalb eines einzelnen Slots (verhindert Slot-Erschöpfung)
36
+
37
+ ## 7.6 Token-Caching
38
+
39
+ - Nur im Speicher (wird niemals auf die Festplatte persistiert)
40
+ - 5-Minuten-Puffer vor Ablauf (TOKEN_BUFFER_MS = 300000)
41
+ - Ausstehende Token-Refresh-Promise verhindert gleichzeitige Token-Anfragen
42
+
43
+ ## 7.7 Eingabe-Sanitisierung
44
+
45
+ OData-Injection-Prävention:
46
+ - `sanitizeIdentifier()` - Regex `[a-zA-Z_][a-zA-Z0-9_]*`
47
+ - `sanitizeGuid()` - GUID-Format-Validierung
48
+ - `escapeODataString()` - Verdopplung einfacher Anführungszeichen
49
+
50
+ ## 7.8 Fehlerbehandlung
51
+
52
+ | HTTP-Status | Verhalten | Retry |
53
+ |-------------|-----------|-------|
54
+ | 2xx | Erfolg | Nein |
55
+ | 401 | Token-Cache leeren, einmal wiederholen | Ja (1x) |
56
+ | 429 | Retry-After respektieren, separater Zähler | Ja (bis zu 10x) |
57
+ | 5xx | Exponentieller Backoff | Ja (bis zu maxRetries) |
58
+ | 404, 403 | Nicht wiederholbar | Nein |
59
+ | Netzwerkfehler | Exponentieller Backoff | Ja |
@@ -0,0 +1,18 @@
1
+ # 8. Authentifizierung
2
+
3
+ ## 8.1 Credential Factory
4
+
5
+ `createCredential(config: AuthConfig)` gibt ein `TokenCredential` (aus @azure/identity) basierend auf der Authentifizierungsmethode zurück:
6
+
7
+ ## 8.2 Vier Authentifizierungsabläufe
8
+
9
+ | Methode | Konfiguration | @azure/identity-Klasse | Anwendungsfall |
10
+ |---------|---------------|------------------------|----------------|
11
+ | client-credentials | tenantId, clientId, clientSecret | ClientSecretCredential | CI/CD, automatisierte Pipelines |
12
+ | interactive | tenantId, clientId? | InteractiveBrowserCredential | Entwickler-Arbeitsplatz |
13
+ | device-code | tenantId, clientId? | DeviceCodeCredential | Headless-CLI-Umgebungen |
14
+ | token | token (string) | StaticTokenCredential | Vorab erworbene Tokens (z.B. aus TokenVault) |
15
+
16
+ ## 8.3 Token-Scope
17
+
18
+ Alle Authentifizierungsabläufe fordern den Scope an: `{environmentUrl}/.default`
@@ -0,0 +1,55 @@
1
+ # 9. Test-Framework
2
+
3
+ ## 9.1 createFormMock
4
+
5
+ ```typescript
6
+ import { createFormMock } from '@xrmforge/testing';
7
+ import type { AccountMainForm, AccountMainFormMockValues } from '../typings/forms/account';
8
+
9
+ const mock = createFormMock<AccountMainForm, AccountMainFormMockValues>({
10
+ name: 'Contoso Ltd',
11
+ statuscode: 0,
12
+ revenue: 1000000,
13
+ });
14
+
15
+ // Verwendung in Tests:
16
+ onLoad(mock.executionContext);
17
+ expect(mock.formContext.getControl('revenue').getVisible()).toBe(true);
18
+ ```
19
+
20
+ **Was gemockt wird:**
21
+ - Attribute: MockAttribute-Instanzen mit getValue/setValue, Dirty-Tracking, onChange-Handlern, Required Level, Submit Mode
22
+ - Steuerelemente: MockControl-Instanzen mit Sichtbarkeits-/Deaktiviert-/Label-/Benachrichtigungszustand
23
+ - UI: Formular-Benachrichtigungen, Tab/Section-Stubs
24
+ - Entität: ID, Entitätsname, Primärattribut
25
+ - Daten: refresh(), save() Stubs, die Promise-ähnliches zurückgeben
26
+ - Navigation: openForm/openAlertDialog Stubs
27
+
28
+ **Lazy-Initialisierung:** Attribute, die über `getAttribute()` angesprochen werden und nicht in den initialen Werten enthalten waren, werden spontan mit null-Wert erstellt.
29
+
30
+ ## 9.2 fireOnChange
31
+
32
+ ```typescript
33
+ mock.fireOnChange('statuscode');
34
+ // Löst alle über getAttribute('statuscode').addOnChange(handler) registrierten Handler aus
35
+ ```
36
+
37
+ Erstellt einen MockEventContext mit dem Attribut als Eventquelle.
38
+
39
+ ## 9.3 setupXrmMock / teardownXrmMock
40
+
41
+ ```typescript
42
+ import { setupXrmMock, teardownXrmMock } from '@xrmforge/testing';
43
+
44
+ beforeEach(() => setupXrmMock());
45
+ afterEach(() => teardownXrmMock());
46
+
47
+ // Mit WebApi-Überschreibungen:
48
+ setupXrmMock({
49
+ webApiOverrides: {
50
+ retrieveMultipleRecords: async () => ({ entities: [{ name: 'Test' }] }),
51
+ },
52
+ });
53
+ ```
54
+
55
+ Richtet ein globales `Xrm`-Objekt auf `globalThis` ein mit minimalen WebApi-, Navigation- und Utility-Stubs.
@@ -0,0 +1,82 @@
1
+ # 10. 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
+ // oder einzelne Regeln auswählen
12
+ ];
13
+ ```
14
+
15
+ ## 10.2 Regeln
16
+
17
+ ### no-xrm-page (error)
18
+
19
+ Verbietet die veraltete `Xrm.Page`-API (entfernt in D365 v9.0+).
20
+
21
+ ```typescript
22
+ // Falsch
23
+ Xrm.Page.getAttribute("name");
24
+
25
+ // Richtig
26
+ const form = executionContext.getFormContext();
27
+ form.getAttribute("name");
28
+ ```
29
+
30
+ ### no-magic-optionset (warn)
31
+
32
+ Verbietet rohe Zahlen (>= 2) in Vergleichen mit `.getValue()`.
33
+
34
+ ```typescript
35
+ // Falsch
36
+ if (attr.getValue() === 595300000) { }
37
+
38
+ // Richtig
39
+ import { StatusCode } from '../typings/optionsets/account';
40
+ if (attr.getValue() === StatusCode.Active) { }
41
+ ```
42
+
43
+ ### no-sync-webapi (error)
44
+
45
+ Verbietet synchrone XMLHttpRequest (`new XMLHttpRequest()` und `.open()` mit `async=false`).
46
+
47
+ ```typescript
48
+ // Falsch
49
+ xhr.open("GET", url, false);
50
+
51
+ // Richtig
52
+ const data = await Xrm.WebApi.retrieveRecord("account", id);
53
+ ```
54
+
55
+ ### require-error-handling (warn)
56
+
57
+ Verlangt try/catch in exportierten async-Funktionen, die mit "on" beginnen (Event-Handler).
58
+
59
+ ```typescript
60
+ // Falsch
61
+ export async function onLoad(ctx) {
62
+ await fetch("/api"); // keine Fehlerbehandlung
63
+ }
64
+
65
+ // Richtig
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
+ Verbietet direkte `window.X = ...` oder `globalThis.X = ...` Zuweisungen. Stattdessen sollen Modul-Exports mit esbuild globalName verwendet werden.
75
+
76
+ ```typescript
77
+ // Falsch
78
+ window.Contoso = { onLoad: function() {} };
79
+
80
+ // Richtig
81
+ export function onLoad(ctx: Xrm.Events.EventContext) {}
82
+ ```