@xrmforge/typegen 0.1.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.
@@ -0,0 +1,1295 @@
1
+ import { TokenCredential } from '@azure/identity';
2
+
3
+ /**
4
+ * @xrmforge/typegen - Error Types
5
+ *
6
+ * Centralized error hierarchy for consistent error handling across the framework.
7
+ * Every error carries a machine-readable code, a human-readable message,
8
+ * and optional context for debugging.
9
+ */
10
+ declare enum ErrorCode {
11
+ AUTH_MISSING_CONFIG = "AUTH_1001",
12
+ AUTH_INVALID_CREDENTIALS = "AUTH_1002",
13
+ AUTH_TOKEN_FAILED = "AUTH_1003",
14
+ AUTH_TOKEN_EXPIRED = "AUTH_1004",
15
+ API_REQUEST_FAILED = "API_2001",
16
+ API_RATE_LIMITED = "API_2002",
17
+ API_NOT_FOUND = "API_2003",
18
+ API_UNAUTHORIZED = "API_2004",
19
+ API_TIMEOUT = "API_2005",
20
+ META_ENTITY_NOT_FOUND = "META_3001",
21
+ META_SOLUTION_NOT_FOUND = "META_3002",
22
+ META_FORM_PARSE_FAILED = "META_3003",
23
+ META_ATTRIBUTE_UNKNOWN_TYPE = "META_3004",
24
+ GEN_OUTPUT_WRITE_FAILED = "GEN_4001",
25
+ GEN_TEMPLATE_FAILED = "GEN_4002",
26
+ GEN_INVALID_IDENTIFIER = "GEN_4003",
27
+ CONFIG_INVALID = "CONFIG_5001",
28
+ CONFIG_FILE_NOT_FOUND = "CONFIG_5002",
29
+ CONFIG_ENV_VAR_MISSING = "CONFIG_5003"
30
+ }
31
+ /**
32
+ * Base error class for all XrmForge errors.
33
+ * Carries a structured error code and optional context object.
34
+ */
35
+ declare class XrmForgeError extends Error {
36
+ readonly code: ErrorCode;
37
+ readonly context: Record<string, unknown>;
38
+ constructor(code: ErrorCode, message: string, context?: Record<string, unknown>);
39
+ }
40
+ /**
41
+ * Authentication-specific error.
42
+ */
43
+ declare class AuthenticationError extends XrmForgeError {
44
+ constructor(code: ErrorCode, message: string, context?: Record<string, unknown>);
45
+ }
46
+ /**
47
+ * Dataverse API request error.
48
+ */
49
+ declare class ApiRequestError extends XrmForgeError {
50
+ readonly statusCode: number | undefined;
51
+ readonly responseBody: string | undefined;
52
+ constructor(code: ErrorCode, message: string, context?: Record<string, unknown> & {
53
+ statusCode?: number;
54
+ responseBody?: string;
55
+ url?: string;
56
+ });
57
+ }
58
+ /**
59
+ * Metadata retrieval or parsing error.
60
+ */
61
+ declare class MetadataError extends XrmForgeError {
62
+ constructor(code: ErrorCode, message: string, context?: Record<string, unknown>);
63
+ }
64
+ /**
65
+ * Type generation or file output error.
66
+ */
67
+ declare class GenerationError extends XrmForgeError {
68
+ constructor(code: ErrorCode, message: string, context?: Record<string, unknown>);
69
+ }
70
+ /**
71
+ * Configuration validation error.
72
+ */
73
+ declare class ConfigError extends XrmForgeError {
74
+ constructor(code: ErrorCode, message: string, context?: Record<string, unknown>);
75
+ }
76
+ /**
77
+ * Type guard to check if an unknown error is an XrmForgeError.
78
+ */
79
+ declare function isXrmForgeError(error: unknown): error is XrmForgeError;
80
+ /**
81
+ * Type guard for API rate limit errors (HTTP 429).
82
+ */
83
+ declare function isRateLimitError(error: unknown): error is ApiRequestError;
84
+
85
+ /**
86
+ * @xrmforge/typegen - Logger
87
+ *
88
+ * Abstracted logging interface that decouples log output from the modules.
89
+ * Supports: CLI (human-readable), CI/CD (structured JSON), silent (library use).
90
+ *
91
+ * Every logger instance carries a `scope` (e.g. "auth", "metadata", "http")
92
+ * so log consumers can filter by origin module.
93
+ *
94
+ * Consumers can provide their own LogSink to integrate with any logging framework.
95
+ */
96
+ declare enum LogLevel {
97
+ DEBUG = 0,
98
+ INFO = 1,
99
+ WARN = 2,
100
+ ERROR = 3,
101
+ SILENT = 4
102
+ }
103
+ interface LogEntry {
104
+ level: LogLevel;
105
+ scope: string;
106
+ message: string;
107
+ context?: Record<string, unknown>;
108
+ timestamp: Date;
109
+ }
110
+ /**
111
+ * Interface for log output destinations.
112
+ * Implement this to route XrmForge logs into your own logging system.
113
+ */
114
+ interface LogSink {
115
+ write(entry: LogEntry): void;
116
+ /**
117
+ * Write an inline progress update (no trailing newline).
118
+ * Used for long-running operations where each entity gets a status indicator.
119
+ *
120
+ * Sinks that don't support inline progress (e.g. JSON) should fall back
121
+ * to writing a regular INFO entry.
122
+ */
123
+ writeProgress(message: string): void;
124
+ /**
125
+ * Complete an inline progress line with a trailing message and newline.
126
+ */
127
+ writeProgressEnd(message: string): void;
128
+ }
129
+ /**
130
+ * Default CLI log sink with human-readable output and ANSI color indicators.
131
+ */
132
+ declare class ConsoleLogSink implements LogSink {
133
+ private static readonly LEVEL_PREFIX;
134
+ write(entry: LogEntry): void;
135
+ writeProgress(message: string): void;
136
+ writeProgressEnd(message: string): void;
137
+ }
138
+ /**
139
+ * Structured JSON log sink for CI/CD pipelines and machine-readable output.
140
+ */
141
+ declare class JsonLogSink implements LogSink {
142
+ write(entry: LogEntry): void;
143
+ writeProgress(message: string): void;
144
+ writeProgressEnd(message: string): void;
145
+ }
146
+ /**
147
+ * Silent log sink that discards all output. Used when running as a library.
148
+ */
149
+ declare class SilentLogSink implements LogSink {
150
+ write(_entry: LogEntry): void;
151
+ writeProgress(_message: string): void;
152
+ writeProgressEnd(_message: string): void;
153
+ }
154
+ /**
155
+ * Logger with scope prefix and configurable sink/level.
156
+ *
157
+ * Usage:
158
+ * ```ts
159
+ * const log = createLogger('auth');
160
+ * log.info('Token acquired', { expiresIn: '3600s' });
161
+ * // Output: [INF] [auth] Token acquired
162
+ * ```
163
+ */
164
+ declare class Logger {
165
+ private readonly scope;
166
+ private readonly getSink;
167
+ private readonly getMinLevel;
168
+ constructor(scope: string, getSink: () => LogSink, getMinLevel: () => LogLevel);
169
+ debug(message: string, context?: Record<string, unknown>): void;
170
+ info(message: string, context?: Record<string, unknown>): void;
171
+ warn(message: string, context?: Record<string, unknown>): void;
172
+ error(message: string, context?: Record<string, unknown>): void;
173
+ /**
174
+ * Write an inline progress update (no newline).
175
+ */
176
+ progress(message: string): void;
177
+ /**
178
+ * Complete an inline progress line.
179
+ */
180
+ progressEnd(message: string): void;
181
+ private log;
182
+ }
183
+ /**
184
+ * Configure logging globally for all @xrmforge modules.
185
+ * Can be called at any time; existing loggers will pick up the new configuration
186
+ * automatically because they reference the shared state via closures.
187
+ *
188
+ * @example
189
+ * ```ts
190
+ * configureLogging({ sink: new JsonLogSink(), minLevel: LogLevel.WARN });
191
+ * ```
192
+ */
193
+ declare function configureLogging(options: {
194
+ sink?: LogSink;
195
+ minLevel?: LogLevel;
196
+ }): void;
197
+ /**
198
+ * Create a scoped logger instance. All modules should use this instead of console.log.
199
+ *
200
+ * The logger reads the global sink and minLevel at each log call (not at creation time),
201
+ * so `configureLogging()` takes effect even on previously created loggers.
202
+ *
203
+ * @param scope - Module identifier shown in log output, e.g. "auth", "metadata", "http"
204
+ */
205
+ declare function createLogger(scope: string): Logger;
206
+
207
+ /**
208
+ * @xrmforge/typegen - Authentication Module
209
+ *
210
+ * Handles authentication to Dataverse Web API using MSAL (@azure/identity).
211
+ * Supports: Client Credentials (Service Principal), Interactive Browser, Device Code.
212
+ *
213
+ * Token acquisition and caching is handled by the DataverseHttpClient.
214
+ * This module is responsible only for creating the correct TokenCredential.
215
+ */
216
+
217
+ type AuthMethod = 'client-credentials' | 'interactive' | 'device-code' | 'token';
218
+ interface ClientCredentialsAuth {
219
+ method: 'client-credentials';
220
+ tenantId: string;
221
+ clientId: string;
222
+ clientSecret: string;
223
+ }
224
+ interface InteractiveAuth {
225
+ method: 'interactive';
226
+ tenantId?: string;
227
+ clientId?: string;
228
+ }
229
+ interface DeviceCodeAuth {
230
+ method: 'device-code';
231
+ tenantId?: string;
232
+ clientId?: string;
233
+ }
234
+ interface TokenAuth {
235
+ method: 'token';
236
+ /** Pre-acquired Bearer token (e.g. from TokenVault, Key Vault, CI/CD secret) */
237
+ token: string;
238
+ }
239
+ type AuthConfig = ClientCredentialsAuth | InteractiveAuth | DeviceCodeAuth | TokenAuth;
240
+ /**
241
+ * Creates an Azure Identity TokenCredential from the provided auth configuration.
242
+ * Validates required fields before attempting credential creation.
243
+ *
244
+ * @throws {AuthenticationError} if required configuration values are missing
245
+ */
246
+ declare function createCredential(config: AuthConfig): TokenCredential;
247
+
248
+ /**
249
+ * @xrmforge/typegen - Dataverse HTTP Client
250
+ *
251
+ * Resilient HTTP client for the Dataverse Web API.
252
+ *
253
+ * Features:
254
+ * - Token caching with 5-minute buffer before expiry
255
+ * - Automatic retry with exponential backoff and jitter
256
+ * - Rate limit awareness (HTTP 429 with Retry-After)
257
+ * - Request timeout via AbortController
258
+ * - Concurrency control (semaphore pattern, NOT recursive)
259
+ * - Automatic OData paging via @odata.nextLink with safety limit
260
+ * - Input sanitization helpers against OData injection
261
+ */
262
+
263
+ interface HttpClientOptions {
264
+ /** Dataverse environment URL, e.g. "https://myorg.crm4.dynamics.com" */
265
+ environmentUrl: string;
266
+ /** Azure Identity credential */
267
+ credential: TokenCredential;
268
+ /** API version (default: "v9.2") */
269
+ apiVersion?: string;
270
+ /** Maximum retry attempts for transient errors (default: 3) */
271
+ maxRetries?: number;
272
+ /** Base delay in ms for exponential backoff (default: 1000) */
273
+ retryBaseDelayMs?: number;
274
+ /** Request timeout in ms (default: 30000) */
275
+ timeoutMs?: number;
276
+ /** Maximum concurrent requests to Dataverse (default: 5) */
277
+ maxConcurrency?: number;
278
+ /** Maximum pages to follow via @odata.nextLink (default: 100, safety limit) */
279
+ maxPages?: number;
280
+ /** Maximum consecutive HTTP 429 retries before giving up (default: 10) */
281
+ maxRateLimitRetries?: number;
282
+ /**
283
+ * Read-only mode (default: true).
284
+ * When true, the client will ONLY allow GET requests and throw an error
285
+ * for any POST, PATCH, PUT, or DELETE attempt.
286
+ *
287
+ * SAFETY: XrmForge typegen is a read-only tool. It must NEVER modify
288
+ * data in Dataverse environments. This flag defaults to true and should
289
+ * only be set to false for the @xrmforge/webapi package (future).
290
+ */
291
+ readOnly?: boolean;
292
+ }
293
+ declare class DataverseHttpClient {
294
+ private readonly baseUrl;
295
+ private readonly apiVersion;
296
+ private readonly credential;
297
+ private readonly maxRetries;
298
+ private readonly retryBaseDelayMs;
299
+ private readonly timeoutMs;
300
+ private readonly maxConcurrency;
301
+ private readonly maxPages;
302
+ private readonly maxRateLimitRetries;
303
+ private readonly readOnly;
304
+ private cachedToken;
305
+ private activeConcurrentRequests;
306
+ private readonly waitQueue;
307
+ constructor(options: HttpClientOptions);
308
+ /**
309
+ * Full API base URL, e.g. "https://myorg.crm4.dynamics.com/api/data/v9.2"
310
+ */
311
+ get apiUrl(): string;
312
+ /**
313
+ * Execute a GET request against the Dataverse Web API.
314
+ * Handles token caching, retries, rate limits, and timeout.
315
+ *
316
+ * @param path - API path (relative or absolute URL)
317
+ * @param signal - Optional AbortSignal to cancel the request
318
+ */
319
+ get<T>(path: string, signal?: AbortSignal): Promise<T>;
320
+ /**
321
+ * Execute a GET request and automatically follow @odata.nextLink for paging.
322
+ * Returns all pages combined into a single array.
323
+ *
324
+ * Safety: Stops after `maxPages` iterations to prevent infinite loops.
325
+ *
326
+ * @param path - API path (relative or absolute URL)
327
+ * @param signal - Optional AbortSignal to cancel the request
328
+ */
329
+ getAll<T>(path: string, signal?: AbortSignal): Promise<T[]>;
330
+ /**
331
+ * Returns true if this client is in read-only mode (the safe default).
332
+ */
333
+ get isReadOnly(): boolean;
334
+ /**
335
+ * Assert that a non-GET operation is allowed.
336
+ * Throws immediately if the client is in read-only mode.
337
+ *
338
+ * @throws {ApiRequestError} always in read-only mode
339
+ * @internal This method exists so that future packages (e.g. @xrmforge/webapi)
340
+ * can reuse the HTTP client for write operations when readOnly is explicitly false.
341
+ */
342
+ assertWriteAllowed(operation: string): void;
343
+ /**
344
+ * Validate that a value is a safe OData identifier (entity name, attribute name).
345
+ * Prevents OData injection by allowing only: starts with letter/underscore,
346
+ * followed by alphanumeric/underscore.
347
+ *
348
+ * @throws {ApiRequestError} if the value contains invalid characters
349
+ */
350
+ static sanitizeIdentifier(value: string): string;
351
+ /**
352
+ * Validate that a value is a properly formatted GUID.
353
+ *
354
+ * @throws {ApiRequestError} if the format is invalid
355
+ */
356
+ static sanitizeGuid(value: string): string;
357
+ /**
358
+ * Escape a string for use inside OData single-quoted string literals.
359
+ * Doubles single quotes to prevent injection.
360
+ */
361
+ static escapeODataString(value: string): string;
362
+ private getToken;
363
+ /**
364
+ * Execute a request within the concurrency semaphore.
365
+ * The semaphore is acquired ONCE per logical request. Retries happen
366
+ * INSIDE the semaphore to avoid the recursive slot exhaustion bug.
367
+ */
368
+ private executeWithConcurrency;
369
+ private acquireSlot;
370
+ private releaseSlot;
371
+ private executeWithRetry;
372
+ private handleHttpError;
373
+ private resolveUrl;
374
+ private calculateBackoff;
375
+ private sleep;
376
+ }
377
+
378
+ /**
379
+ * @xrmforge/typegen - Metadata Types
380
+ *
381
+ * TypeScript interfaces for Dataverse Metadata API responses.
382
+ * These types model the JSON structures returned by the EntityDefinitions,
383
+ * Attributes, SystemForms, and GlobalOptionSetDefinitions endpoints.
384
+ */
385
+ interface LocalizedLabel {
386
+ Label: string;
387
+ LanguageCode: number;
388
+ }
389
+ interface Label {
390
+ LocalizedLabels: LocalizedLabel[];
391
+ UserLocalizedLabel: LocalizedLabel | null;
392
+ }
393
+ interface EntityMetadata {
394
+ LogicalName: string;
395
+ SchemaName: string;
396
+ EntitySetName: string;
397
+ DisplayName: Label;
398
+ PrimaryIdAttribute: string;
399
+ PrimaryNameAttribute: string;
400
+ OwnershipType: string;
401
+ IsCustomEntity: boolean;
402
+ LogicalCollectionName: string;
403
+ MetadataId: string;
404
+ Attributes?: AttributeMetadata[];
405
+ }
406
+ interface AttributeMetadata {
407
+ '@odata.type'?: string;
408
+ LogicalName: string;
409
+ SchemaName: string;
410
+ AttributeType: string;
411
+ AttributeTypeName?: {
412
+ Value: string;
413
+ };
414
+ DisplayName: Label;
415
+ IsPrimaryId: boolean;
416
+ IsPrimaryName: boolean;
417
+ RequiredLevel: {
418
+ Value: string;
419
+ };
420
+ IsValidForRead: boolean;
421
+ IsValidForCreate: boolean;
422
+ IsValidForUpdate: boolean;
423
+ MetadataId: string;
424
+ }
425
+ interface StringAttributeMetadata extends AttributeMetadata {
426
+ MaxLength: number;
427
+ FormatName: {
428
+ Value: string;
429
+ } | null;
430
+ }
431
+ interface IntegerAttributeMetadata extends AttributeMetadata {
432
+ MaxValue: number;
433
+ MinValue: number;
434
+ }
435
+ interface DecimalAttributeMetadata extends AttributeMetadata {
436
+ MaxValue: number;
437
+ MinValue: number;
438
+ Precision: number;
439
+ }
440
+ interface MoneyAttributeMetadata extends AttributeMetadata {
441
+ MaxValue: number;
442
+ MinValue: number;
443
+ Precision: number;
444
+ PrecisionSource: number;
445
+ }
446
+ interface DateTimeAttributeMetadata extends AttributeMetadata {
447
+ DateTimeBehavior: {
448
+ Value: string;
449
+ };
450
+ Format: string;
451
+ }
452
+ interface LookupAttributeMetadata extends AttributeMetadata {
453
+ Targets: string[];
454
+ }
455
+ interface PicklistAttributeMetadata extends AttributeMetadata {
456
+ OptionSet: OptionSetMetadata | null;
457
+ GlobalOptionSet: OptionSetMetadata | null;
458
+ }
459
+ interface StatusAttributeMetadata extends AttributeMetadata {
460
+ OptionSet: OptionSetMetadata | null;
461
+ }
462
+ interface StateAttributeMetadata extends AttributeMetadata {
463
+ OptionSet: OptionSetMetadata | null;
464
+ }
465
+ interface OptionMetadata {
466
+ Value: number;
467
+ Label: Label;
468
+ Description: Label;
469
+ Color: string | null;
470
+ }
471
+ interface OptionSetMetadata {
472
+ '@odata.type'?: string;
473
+ Name: string;
474
+ DisplayName: Label;
475
+ IsCustomOptionSet: boolean;
476
+ IsGlobal: boolean;
477
+ OptionSetType: string;
478
+ Options: OptionMetadata[];
479
+ MetadataId: string;
480
+ }
481
+ interface SystemFormMetadata {
482
+ name: string;
483
+ formid: string;
484
+ formxml: string;
485
+ description: string | null;
486
+ isdefault: boolean;
487
+ }
488
+ /** Parsed data-bound control from FormXml (bound to an attribute) */
489
+ interface FormControl {
490
+ /** Control ID (often same as datafieldname) */
491
+ id: string;
492
+ /** Attribute logical name this control is bound to */
493
+ datafieldname: string;
494
+ /** Control class ID (GUID identifying the control type) */
495
+ classid: string;
496
+ }
497
+ /** Type of special (non-data-bound) control on a form */
498
+ type SpecialControlType = 'subgrid' | 'editablegrid' | 'quickview' | 'webresource' | 'iframe' | 'notes' | 'map' | 'timer' | 'unknown';
499
+ /** Parsed special control from FormXml (subgrid, quick view, web resource, etc.) */
500
+ interface FormSpecialControl {
501
+ /** Control ID (used for getControl) */
502
+ id: string;
503
+ /** Control class ID (GUID) */
504
+ classid: string;
505
+ /** Resolved control type */
506
+ controlType: SpecialControlType;
507
+ /** Target entity for subgrids (from parameters) */
508
+ targetEntityType?: string;
509
+ /** Relationship name for subgrids (from parameters) */
510
+ relationshipName?: string;
511
+ /** Web resource name (for web resource controls) */
512
+ webResourceName?: string;
513
+ }
514
+ /** Parsed tab from FormXml */
515
+ interface FormTab {
516
+ name: string;
517
+ /** Tab label (for display, may be localized) */
518
+ label?: string;
519
+ /** Whether the tab is visible by default */
520
+ visible?: boolean;
521
+ sections: FormSection[];
522
+ }
523
+ /** Parsed section from FormXml */
524
+ interface FormSection {
525
+ name: string;
526
+ /** Section label */
527
+ label?: string;
528
+ /** Whether the section is visible by default */
529
+ visible?: boolean;
530
+ controls: FormControl[];
531
+ /** Special controls in this section (subgrids, quick views, etc.) */
532
+ specialControls: FormSpecialControl[];
533
+ }
534
+ /** Parsed form structure */
535
+ interface ParsedForm {
536
+ name: string;
537
+ formId: string;
538
+ isDefault: boolean;
539
+ tabs: FormTab[];
540
+ /** All data-bound controls across all tabs/sections (flattened) */
541
+ allControls: FormControl[];
542
+ /** All special controls across all tabs/sections (flattened) */
543
+ allSpecialControls: FormSpecialControl[];
544
+ }
545
+ interface OneToManyRelationshipMetadata {
546
+ SchemaName: string;
547
+ ReferencingEntity: string;
548
+ ReferencingAttribute: string;
549
+ ReferencedEntity: string;
550
+ ReferencedAttribute: string;
551
+ MetadataId: string;
552
+ }
553
+ interface ManyToManyRelationshipMetadata {
554
+ SchemaName: string;
555
+ Entity1LogicalName: string;
556
+ Entity2LogicalName: string;
557
+ IntersectEntityName: string;
558
+ MetadataId: string;
559
+ }
560
+ interface SolutionComponent {
561
+ objectid: string;
562
+ componenttype: number;
563
+ }
564
+ /** Complete metadata for a single entity, ready for type generation */
565
+ interface EntityTypeInfo {
566
+ entity: EntityMetadata;
567
+ attributes: AttributeMetadata[];
568
+ picklistAttributes: PicklistAttributeMetadata[];
569
+ lookupAttributes: LookupAttributeMetadata[];
570
+ statusAttributes: StatusAttributeMetadata[];
571
+ stateAttributes: StateAttributeMetadata[];
572
+ forms: ParsedForm[];
573
+ oneToManyRelationships: OneToManyRelationshipMetadata[];
574
+ manyToManyRelationships: ManyToManyRelationshipMetadata[];
575
+ }
576
+
577
+ /**
578
+ * @xrmforge/typegen - Metadata Client
579
+ *
580
+ * High-level client for querying Dataverse Metadata API endpoints.
581
+ * Built on top of DataverseHttpClient for resilient HTTP communication.
582
+ *
583
+ * Provides methods for:
584
+ * - Entity metadata with attributes
585
+ * - Typed attribute queries (Picklist, Lookup, Status/State)
586
+ * - Form metadata (SystemForms + FormXml parsing)
587
+ * - Global OptionSet definitions
588
+ * - Solution-based entity filtering
589
+ * - Relationship metadata (1:N, N:N)
590
+ */
591
+
592
+ declare class MetadataClient {
593
+ private readonly http;
594
+ constructor(httpClient: DataverseHttpClient);
595
+ /**
596
+ * Get metadata for a single entity by LogicalName, including all attributes.
597
+ *
598
+ * @throws {MetadataError} if the entity is not found
599
+ */
600
+ getEntityWithAttributes(logicalName: string): Promise<EntityMetadata>;
601
+ /**
602
+ * List all entities (without attributes) for discovery.
603
+ * Use `$filter` parameter to narrow results.
604
+ */
605
+ listEntities(filter?: string): Promise<EntityMetadata[]>;
606
+ /**
607
+ * Get all Picklist attributes with their OptionSets for an entity.
608
+ * Includes both local and global OptionSets.
609
+ */
610
+ getPicklistAttributes(logicalName: string): Promise<PicklistAttributeMetadata[]>;
611
+ /**
612
+ * Get all Lookup attributes with their target entity names.
613
+ */
614
+ getLookupAttributes(logicalName: string): Promise<LookupAttributeMetadata[]>;
615
+ /**
616
+ * Get Status attributes (statuscode) with their OptionSets.
617
+ */
618
+ getStatusAttributes(logicalName: string): Promise<StatusAttributeMetadata[]>;
619
+ /**
620
+ * Get State attributes (statecode) with their OptionSets.
621
+ */
622
+ getStateAttributes(logicalName: string): Promise<StateAttributeMetadata[]>;
623
+ /**
624
+ * Get and parse Main forms (type=2) for an entity.
625
+ * Returns parsed form structures with tabs, sections, and controls.
626
+ */
627
+ getMainForms(logicalName: string): Promise<ParsedForm[]>;
628
+ /**
629
+ * Get a global OptionSet by its exact name.
630
+ */
631
+ getGlobalOptionSet(name: string): Promise<OptionSetMetadata>;
632
+ /**
633
+ * List all global OptionSets (names and types only).
634
+ */
635
+ listGlobalOptionSets(): Promise<OptionSetMetadata[]>;
636
+ /**
637
+ * Get all 1:N relationships where this entity is the referenced (parent) entity.
638
+ */
639
+ getOneToManyRelationships(logicalName: string): Promise<OneToManyRelationshipMetadata[]>;
640
+ /**
641
+ * Get all N:N relationships for an entity.
642
+ */
643
+ getManyToManyRelationships(logicalName: string): Promise<ManyToManyRelationshipMetadata[]>;
644
+ /**
645
+ * Get all entity LogicalNames that belong to a specific solution.
646
+ * Resolves SolutionComponent MetadataIds to EntityDefinition LogicalNames.
647
+ *
648
+ * @param solutionUniqueName - The unique name of the solution
649
+ * @returns Array of entity LogicalNames (e.g. ["account", "contact"])
650
+ */
651
+ getEntityNamesForSolution(solutionUniqueName: string): Promise<string[]>;
652
+ /**
653
+ * Get all entity LogicalNames from multiple solutions, merged and deduplicated.
654
+ *
655
+ * @param solutionUniqueNames - Array of solution unique names
656
+ * @returns Deduplicated array of entity LogicalNames
657
+ */
658
+ getEntityNamesForSolutions(solutionUniqueNames: string[]): Promise<string[]>;
659
+ /**
660
+ * Fetch complete metadata for a single entity: all attributes (typed),
661
+ * forms, and relationships. This is the primary method for type generation.
662
+ *
663
+ * Makes 7 parallel API calls per entity for optimal performance.
664
+ */
665
+ getEntityTypeInfo(logicalName: string): Promise<EntityTypeInfo>;
666
+ /**
667
+ * Fetch complete metadata for multiple entities in parallel.
668
+ * Respects the HTTP client's concurrency limit automatically.
669
+ */
670
+ getMultipleEntityTypeInfos(logicalNames: string[]): Promise<EntityTypeInfo[]>;
671
+ private getRelationships;
672
+ }
673
+
674
+ /**
675
+ * @xrmforge/typegen - Metadata Cache
676
+ *
677
+ * File-system based metadata cache using Dataverse's RetrieveMetadataChanges
678
+ * ServerVersionStamp for efficient delta detection.
679
+ *
680
+ * On first run: full metadata retrieval, saved to .xrmforge/cache/metadata.json
681
+ * On subsequent runs: delta query with stored VersionStamp, only changed entities refreshed
682
+ * On expired stamp (90-day window or system maintenance): automatic full reload
683
+ *
684
+ * @see https://learn.microsoft.com/en-us/power-apps/developer/data-platform/cache-schema-data
685
+ */
686
+
687
+ interface CacheManifest {
688
+ /** XrmForge version that created this cache */
689
+ version: string;
690
+ /** Dataverse environment URL */
691
+ environmentUrl: string;
692
+ /** ServerVersionStamp from last RetrieveMetadataChanges call */
693
+ serverVersionStamp: string | null;
694
+ /** ISO timestamp of last full or delta refresh */
695
+ lastRefreshed: string;
696
+ /** Entity logical names in this cache */
697
+ entities: string[];
698
+ }
699
+ interface CacheData {
700
+ manifest: CacheManifest;
701
+ entityTypeInfos: Record<string, EntityTypeInfo>;
702
+ }
703
+ declare class MetadataCache {
704
+ private readonly cacheDir;
705
+ private readonly cacheFilePath;
706
+ /**
707
+ * @param projectRoot - Root directory of the project (where .xrmforge/ will be created)
708
+ */
709
+ constructor(projectRoot: string);
710
+ /**
711
+ * Load cached metadata from disk.
712
+ * Returns null if no cache exists, cache is for a different environment,
713
+ * or cache format is incompatible.
714
+ */
715
+ load(environmentUrl: string): Promise<CacheData | null>;
716
+ /**
717
+ * Save metadata to the file-system cache.
718
+ */
719
+ save(environmentUrl: string, entityTypeInfos: Record<string, EntityTypeInfo>, serverVersionStamp: string | null): Promise<void>;
720
+ /**
721
+ * Get the stored ServerVersionStamp for delta queries.
722
+ * Returns null if no cache exists.
723
+ */
724
+ getVersionStamp(environmentUrl: string): Promise<string | null>;
725
+ /**
726
+ * Update specific entities in the cache (delta update).
727
+ * Merges new/changed entities into the existing cache.
728
+ */
729
+ updateEntities(environmentUrl: string, updatedEntities: Record<string, EntityTypeInfo>, newVersionStamp: string | null): Promise<void>;
730
+ /**
731
+ * Remove specific entities from the cache (for deleted entities).
732
+ */
733
+ removeEntities(environmentUrl: string, deletedEntityNames: string[], newVersionStamp: string | null): Promise<void>;
734
+ /**
735
+ * Delete the entire cache.
736
+ */
737
+ clear(): Promise<void>;
738
+ /**
739
+ * Check if a cache file exists.
740
+ */
741
+ exists(): Promise<boolean>;
742
+ }
743
+
744
+ /**
745
+ * @xrmforge/typegen - Label Utilities
746
+ *
747
+ * Extracts and formats localized labels from Dataverse metadata.
748
+ * Supports dual-language output (Goldene Regel 15):
749
+ * - Primary language for identifiers and first JSDoc line
750
+ * - Secondary language as optional addition in JSDoc
751
+ *
752
+ * Format: "Primary Label | Sekundäres Label"
753
+ * If secondary language is not available: "Primary Label" only.
754
+ */
755
+
756
+ interface LabelConfig {
757
+ /** Primary language LCID (used for identifiers and first JSDoc line). Default: 1033 (English) */
758
+ primaryLanguage: number;
759
+ /** Optional secondary language LCID (added as comment). Example: 1031 (German) */
760
+ secondaryLanguage?: number;
761
+ }
762
+ /** Default: English only */
763
+ declare const DEFAULT_LABEL_CONFIG: LabelConfig;
764
+ /**
765
+ * Extract a label string for the primary language.
766
+ * Falls back to UserLocalizedLabel if the specific LCID is not found.
767
+ * Returns empty string if no label is available.
768
+ */
769
+ declare function getPrimaryLabel(label: Label | null | undefined, config: LabelConfig): string;
770
+ /**
771
+ * Extract a dual-language JSDoc string.
772
+ * Returns "Primary | Secondary" if both languages available,
773
+ * or just "Primary" if secondary is missing or not configured.
774
+ */
775
+ declare function getJSDocLabel(label: Label | null | undefined, config: LabelConfig): string;
776
+ /**
777
+ * Convert a label string to a valid TypeScript identifier (PascalCase).
778
+ * Transliterates German umlauts (ä to ae, ö to oe, ü to ue, ß to ss),
779
+ * then removes remaining invalid characters.
780
+ *
781
+ * @returns A valid TypeScript identifier, or null if the label cannot be converted
782
+ */
783
+ declare function labelToIdentifier(label: string): string | null;
784
+ /**
785
+ * Generate unique enum member names from OptionSet labels.
786
+ * Handles duplicates by appending _{Value} to colliding names.
787
+ * Falls back to Value_{numericValue} for unconvertible labels.
788
+ *
789
+ * @param options - Array of { Value, Label } from OptionSetMetadata
790
+ * @param config - Label configuration for language selection
791
+ * @returns Array of { name, value, jsDocLabel } for enum generation
792
+ */
793
+ declare function generateEnumMembers(options: Array<{
794
+ Value: number;
795
+ Label: Label;
796
+ }>, config: LabelConfig): Array<{
797
+ name: string;
798
+ value: number;
799
+ jsDocLabel: string;
800
+ }>;
801
+ /**
802
+ * Build the LabelLanguages query parameter for Dataverse Metadata API.
803
+ * Returns the parameter string to append to metadata queries.
804
+ *
805
+ * @example
806
+ * getLabelLanguagesParam({ primaryLanguage: 1033, secondaryLanguage: 1031 })
807
+ * // Returns "&LabelLanguages=1033,1031"
808
+ */
809
+ declare function getLabelLanguagesParam(config: LabelConfig): string;
810
+
811
+ /**
812
+ * @xrmforge/typegen - XML Parser Abstraction
813
+ *
814
+ * Interface for XML parsing, decoupled from any specific parser library.
815
+ * Allows swapping the underlying parser (currently fast-xml-parser)
816
+ * by implementing a single interface. (Goldene Regel 14)
817
+ */
818
+ /**
819
+ * Parsed XML element with attributes and children.
820
+ */
821
+ interface XmlElement {
822
+ /** Element tag name */
823
+ tag: string;
824
+ /** Element attributes as key-value pairs */
825
+ attributes: Record<string, string>;
826
+ /** Child elements */
827
+ children: XmlElement[];
828
+ /** Text content (if any) */
829
+ text?: string;
830
+ }
831
+ /**
832
+ * Abstraction for XML parsing. Implementations must convert an XML string
833
+ * into a tree of XmlElement objects.
834
+ *
835
+ * To swap the underlying parser library, implement this interface
836
+ * and pass it to the FormXml parser. Only this file needs to change.
837
+ */
838
+ interface XmlParser {
839
+ parse(xml: string): XmlElement;
840
+ }
841
+ /**
842
+ * XML parser implementation using fast-xml-parser.
843
+ * Zero dependencies (fast-xml-parser itself has none), 26 KB minified.
844
+ */
845
+ declare class FastXmlParser implements XmlParser {
846
+ private readonly parser;
847
+ constructor();
848
+ parse(xml: string): XmlElement;
849
+ private convertToXmlElement;
850
+ private convertNode;
851
+ }
852
+ /** Default parser instance. Use this unless you need a custom parser. */
853
+ declare const defaultXmlParser: XmlParser;
854
+
855
+ /**
856
+ * @xrmforge/typegen - FormXml Parser
857
+ *
858
+ * Parses Dataverse FormXml into structured TypeScript objects.
859
+ * Extracts tabs, sections, controls (data-bound + special) for generating
860
+ * typed FormContext interfaces with compile-time field validation.
861
+ *
862
+ * Uses the XmlParser abstraction (Goldene Regel 14) instead of regex.
863
+ */
864
+
865
+ /**
866
+ * Parse a SystemFormMetadata response into a structured ParsedForm.
867
+ *
868
+ * @param form - The system form metadata from the API
869
+ * @param parser - XML parser to use (defaults to fast-xml-parser)
870
+ * @throws {MetadataError} if the formxml cannot be parsed
871
+ */
872
+ declare function parseForm(form: SystemFormMetadata, parser?: XmlParser): ParsedForm;
873
+ /**
874
+ * Extract all data-bound control field names from FormXml (flattened).
875
+ * Simpler alternative to full parsing when only the field list is needed.
876
+ */
877
+ declare function extractControlFields(formxml: string, parser?: XmlParser): string[];
878
+
879
+ /**
880
+ * @xrmforge/typegen - Type Mapping
881
+ *
882
+ * Maps Dataverse AttributeType values to TypeScript types for:
883
+ * 1. Entity interfaces (Web API data types)
884
+ * 2. Form interfaces (Xrm.Attributes.* types from @types/xrm)
885
+ * 3. Control interfaces (Xrm.Controls.* types from @types/xrm)
886
+ *
887
+ * This is the bridge between Dataverse metadata and generated TypeScript.
888
+ * Goldene Regel 1: All types extend @types/xrm, never replace.
889
+ */
890
+
891
+ /**
892
+ * Map Dataverse AttributeType to TypeScript type for entity interfaces.
893
+ * These represent the raw data types returned by the Web API.
894
+ *
895
+ * @param attributeType - The AttributeType from Dataverse metadata
896
+ * @param isLookup - Whether this is a lookup field (uses _fieldname_value pattern)
897
+ * @returns TypeScript type string (e.g. "string", "number", "boolean")
898
+ */
899
+ declare function getEntityPropertyType(attributeType: string, isLookup?: boolean): string;
900
+ /**
901
+ * Map Dataverse AttributeType to Xrm.Attributes.* type for form interfaces.
902
+ * These represent the getAttribute() return types on FormContext.
903
+ *
904
+ * @param attributeType - The AttributeType from Dataverse metadata
905
+ * @returns Fully qualified Xrm attribute type string
906
+ */
907
+ declare function getFormAttributeType(attributeType: string): string;
908
+ /**
909
+ * Map Dataverse AttributeType to Xrm.Controls.* type for form interfaces.
910
+ * These represent the getControl() return types on FormContext.
911
+ *
912
+ * @param attributeType - The AttributeType from Dataverse metadata
913
+ * @returns Fully qualified Xrm control type string
914
+ */
915
+ declare function getFormControlType(attributeType: string): string;
916
+ /**
917
+ * Convert a Dataverse LogicalName to a safe TypeScript identifier.
918
+ * Validates that the result is a valid identifier.
919
+ *
920
+ * @param logicalName - Dataverse field or entity logical name
921
+ * @returns A valid TypeScript identifier
922
+ * @throws Never throws; returns the input if already valid, prefixes with _ if starts with digit
923
+ */
924
+ declare function toSafeIdentifier(logicalName: string): string;
925
+ /**
926
+ * Convert a Dataverse LogicalName to PascalCase for use as interface/type name.
927
+ *
928
+ * @example
929
+ * toPascalCase('account') // 'Account'
930
+ * toPascalCase('markant_cdhcontactsource') // 'MarkantCdhcontactsource'
931
+ */
932
+ declare function toPascalCase(logicalName: string): string;
933
+ /**
934
+ * Convert a lookup attribute LogicalName to its Web API value property name.
935
+ * In the Web API, lookup fields are represented as `_fieldname_value`.
936
+ *
937
+ * @example
938
+ * toLookupValueProperty('primarycontactid') // '_primarycontactid_value'
939
+ * toLookupValueProperty('ownerid') // '_ownerid_value'
940
+ */
941
+ declare function toLookupValueProperty(logicalName: string): string;
942
+ /**
943
+ * Determine if an attribute is a single-value lookup type.
944
+ * PartyList is NOT included: it's a collection navigation property (ActivityParty[]),
945
+ * not a single _fieldname_value property in the Web API.
946
+ */
947
+ declare function isLookupType(attributeType: string): boolean;
948
+ /**
949
+ * Determine if an attribute should be included in entity interfaces.
950
+ * Excludes virtual/calculated fields that are not readable via Web API.
951
+ *
952
+ * Filtered types:
953
+ * - Virtual, CalendarRules: not readable via Web API
954
+ * - ManagedProperty: solution metadata (iscustomizable etc.), not business data
955
+ * - EntityName: internal companion fields for lookups; entity type info is only
956
+ * available via @Microsoft.Dynamics.CRM.lookuplogicalname OData annotation,
957
+ * not as a standalone property in Web API responses
958
+ */
959
+ declare function shouldIncludeInEntityInterface(attr: AttributeMetadata): boolean;
960
+
961
+ /**
962
+ * @xrmforge/typegen - Generator-specific Label Utilities
963
+ *
964
+ * This module contains ONLY generator-specific label functions.
965
+ * Core label extraction (getPrimaryLabel, getJSDocLabel, LabelConfig etc.)
966
+ * is provided by the canonical implementation in metadata/labels.ts (R6-02).
967
+ *
968
+ * Re-exports from metadata/labels.ts are provided for convenience.
969
+ */
970
+
971
+ /**
972
+ * Extract the secondary language label, if configured and available.
973
+ * Returns undefined if no secondary language is configured or label not found.
974
+ */
975
+ declare function getSecondaryLabel(label: Label, config: LabelConfig): string | undefined;
976
+ /**
977
+ * Disambiguate duplicate enum member names by appending the numeric value.
978
+ * Only the second and subsequent duplicates get the suffix.
979
+ * Re-checks that the suffixed name doesn't collide with an existing name.
980
+ *
981
+ * @example
982
+ * disambiguateEnumMembers([
983
+ * { name: "Active", value: 1 },
984
+ * { name: "Active", value: 2 },
985
+ * ])
986
+ * // [{ name: "Active", value: 1 }, { name: "Active_2", value: 2 }]
987
+ *
988
+ * // Edge case: "Active_2" already exists as a label-derived name
989
+ * disambiguateEnumMembers([
990
+ * { name: "Active", value: 1 },
991
+ * { name: "Active", value: 2 },
992
+ * { name: "Active_2", value: 3 },
993
+ * ])
994
+ * // [{ name: "Active", value: 1 }, { name: "Active_2_v2", value: 2 }, { name: "Active_2", value: 3 }]
995
+ */
996
+ declare function disambiguateEnumMembers(members: Array<{
997
+ name: string;
998
+ value: number;
999
+ }>): Array<{
1000
+ name: string;
1001
+ value: number;
1002
+ }>;
1003
+
1004
+ /**
1005
+ * @xrmforge/typegen - Entity Interface Generator
1006
+ *
1007
+ * Generates TypeScript declaration files (.d.ts) for Dataverse entity interfaces.
1008
+ * These interfaces represent the data types returned by the Web API.
1009
+ *
1010
+ * Output pattern:
1011
+ * ```typescript
1012
+ * declare namespace XrmForge.Entities {
1013
+ * interface Account {
1014
+ * accountid: string | null;
1015
+ * name: string | null;
1016
+ * // ...
1017
+ * }
1018
+ * }
1019
+ * ```
1020
+ */
1021
+
1022
+ /** Options for entity interface generation */
1023
+ interface EntityGeneratorOptions {
1024
+ /** Label configuration for dual-language JSDoc comments */
1025
+ labelConfig?: LabelConfig;
1026
+ /** Namespace for generated types (default: "XrmForge.Entities") */
1027
+ namespace?: string;
1028
+ }
1029
+ /**
1030
+ * Generate a TypeScript declaration for an entity interface.
1031
+ *
1032
+ * @param info - Complete entity metadata (from MetadataClient.getEntityTypeInfo)
1033
+ * @param options - Generator options
1034
+ * @returns TypeScript declaration string (.d.ts content)
1035
+ */
1036
+ declare function generateEntityInterface(info: EntityTypeInfo, options?: EntityGeneratorOptions): string;
1037
+
1038
+ /**
1039
+ * @xrmforge/typegen - OptionSet Enum Generator
1040
+ *
1041
+ * Generates TypeScript const enums from Dataverse OptionSet metadata.
1042
+ * Uses const enum because D365 form scripts have no module system at runtime,
1043
+ * so enum values must be inlined at compile time.
1044
+ *
1045
+ * Output pattern:
1046
+ * ```typescript
1047
+ * declare namespace XrmForge.OptionSets {
1048
+ * const enum AccountCategoryCode {
1049
+ * PreferredCustomer = 1,
1050
+ * Standard = 2,
1051
+ * }
1052
+ * }
1053
+ * ```
1054
+ */
1055
+
1056
+ /** Options for OptionSet enum generation */
1057
+ interface OptionSetGeneratorOptions {
1058
+ /** Label configuration for dual-language JSDoc comments */
1059
+ labelConfig?: LabelConfig;
1060
+ /** Namespace for generated types (default: "XrmForge.OptionSets") */
1061
+ namespace?: string;
1062
+ }
1063
+ /**
1064
+ * Generate a TypeScript const enum declaration from an OptionSet.
1065
+ *
1066
+ * @param optionSet - OptionSet metadata from Dataverse
1067
+ * @param entityLogicalName - Entity this OptionSet belongs to (for naming local OptionSets)
1068
+ * @param attributeSchemaName - Attribute schema name (for naming local OptionSets)
1069
+ * @param options - Generator options
1070
+ * @returns TypeScript const enum declaration string
1071
+ */
1072
+ declare function generateOptionSetEnum(optionSet: OptionSetMetadata, _entityLogicalName: string, attributeSchemaName: string, options?: OptionSetGeneratorOptions): string;
1073
+ /**
1074
+ * Generate multiple OptionSet enums for all picklist attributes of an entity.
1075
+ * Handles both local and global OptionSets.
1076
+ *
1077
+ * @param picklistAttributes - Picklist attributes with their OptionSet metadata
1078
+ * @param entityLogicalName - Entity logical name
1079
+ * @param options - Generator options
1080
+ * @returns Array of { enumName, content } for each generated enum
1081
+ */
1082
+ declare function generateEntityOptionSets(picklistAttributes: Array<{
1083
+ SchemaName: string;
1084
+ OptionSet: OptionSetMetadata | null;
1085
+ GlobalOptionSet: OptionSetMetadata | null;
1086
+ }>, entityLogicalName: string, options?: OptionSetGeneratorOptions): Array<{
1087
+ enumName: string;
1088
+ content: string;
1089
+ }>;
1090
+
1091
+ /**
1092
+ * @xrmforge/typegen - Form Interface Generator
1093
+ *
1094
+ * Generates TypeScript form interfaces with compile-time field validation.
1095
+ *
1096
+ * Architecture:
1097
+ * 1. Union Type (LeadFormFields): restricts getAttribute to form-specific fields only
1098
+ * 2. Mapped Return Type (LeadAttributeMap): maps field name to correct Xrm type
1099
+ * 3. Generic getAttribute<K>: returns the exact type for each field
1100
+ * 4. Fields const enum: provides autocomplete with dual-language labels
1101
+ * 5. NO fallback getAttribute(name: string): unknown fields are compile errors
1102
+ *
1103
+ * Output pattern:
1104
+ * ```typescript
1105
+ * declare namespace XrmForge.Forms.Account {
1106
+ * type AccountMainFormFields = "name" | "telephone1" | "revenue";
1107
+ *
1108
+ * type AccountMainFormAttributeMap = {
1109
+ * name: Xrm.Attributes.StringAttribute;
1110
+ * telephone1: Xrm.Attributes.StringAttribute;
1111
+ * revenue: Xrm.Attributes.NumberAttribute;
1112
+ * };
1113
+ *
1114
+ * type AccountMainFormControlMap = {
1115
+ * name: Xrm.Controls.StringControl;
1116
+ * telephone1: Xrm.Controls.StringControl;
1117
+ * revenue: Xrm.Controls.NumberControl;
1118
+ * };
1119
+ *
1120
+ * const enum AccountMainFormFields {
1121
+ * Name = 'name',
1122
+ * Telephone1 = 'telephone1',
1123
+ * Revenue = 'revenue',
1124
+ * }
1125
+ *
1126
+ * interface AccountMainForm extends Omit<Xrm.FormContext, 'getAttribute' | 'getControl'> {
1127
+ * getAttribute<K extends AccountMainFormFields>(name: K): AccountMainFormAttributeMap[K];
1128
+ * getAttribute(index: number): Xrm.Attributes.Attribute;
1129
+ * getAttribute(): Xrm.Attributes.Attribute[];
1130
+ * getControl<K extends AccountMainFormFields>(name: K): AccountMainFormControlMap[K];
1131
+ * getControl(index: number): Xrm.Controls.Control;
1132
+ * getControl(): Xrm.Controls.Control[];
1133
+ * }
1134
+ * }
1135
+ * ```
1136
+ */
1137
+
1138
+ /** Options for form interface generation */
1139
+ interface FormGeneratorOptions {
1140
+ /** Label configuration for dual-language JSDoc comments */
1141
+ labelConfig?: LabelConfig;
1142
+ /** Namespace prefix for generated types (default: "XrmForge.Forms") */
1143
+ namespacePrefix?: string;
1144
+ /** Form types to include (default: [2] = Main only) */
1145
+ formTypes?: number[];
1146
+ }
1147
+ /**
1148
+ * Generate a complete form declaration: union type, mapped types, fields enum, and interface.
1149
+ *
1150
+ * @param form - Parsed form structure (from FormXml parser)
1151
+ * @param entityLogicalName - Entity this form belongs to
1152
+ * @param attributeMap - Map of LogicalName to AttributeMetadata for type resolution
1153
+ * @param options - Generator options
1154
+ * @returns TypeScript declaration string
1155
+ */
1156
+ declare function generateFormInterface(form: ParsedForm, entityLogicalName: string, attributeMap: Map<string, AttributeMetadata>, options?: FormGeneratorOptions, baseNameOverride?: string): string;
1157
+ /**
1158
+ * Generate form interfaces for all forms of an entity.
1159
+ *
1160
+ * @param forms - Parsed forms (from FormXml parser)
1161
+ * @param entityLogicalName - Entity logical name
1162
+ * @param attributes - All attributes of the entity (for type resolution)
1163
+ * @param options - Generator options
1164
+ * @returns Array of { formName, interfaceName, content }
1165
+ */
1166
+ declare function generateEntityForms(forms: ParsedForm[], entityLogicalName: string, attributes: AttributeMetadata[], options?: FormGeneratorOptions): Array<{
1167
+ formName: string;
1168
+ interfaceName: string;
1169
+ content: string;
1170
+ }>;
1171
+
1172
+ /**
1173
+ * @xrmforge/typegen - Orchestrator Types
1174
+ *
1175
+ * Configuration and result types for the type generation orchestrator.
1176
+ */
1177
+
1178
+ /** Configuration for the type generation process */
1179
+ interface GenerateConfig {
1180
+ /** Dataverse environment URL (e.g. "https://myorg.crm4.dynamics.com") */
1181
+ environmentUrl: string;
1182
+ /** Entity logical names to generate types for (merged with solution entities if both set) */
1183
+ entities: string[];
1184
+ /** Solution unique names to discover entities automatically (merged with entities, deduplicated) */
1185
+ solutionNames?: string[];
1186
+ /** Output directory for generated .d.ts files */
1187
+ outputDir: string;
1188
+ /** Label language configuration */
1189
+ labelConfig: LabelConfig;
1190
+ /** Whether to generate entity interfaces (default: true) */
1191
+ generateEntities?: boolean;
1192
+ /** Whether to generate form interfaces (default: true) */
1193
+ generateForms?: boolean;
1194
+ /** Whether to generate OptionSet enums (default: true) */
1195
+ generateOptionSets?: boolean;
1196
+ /**
1197
+ * Whether to use metadata cache for faster re-generation.
1198
+ * @alpha Not yet implemented. Setting this to true will throw a ConfigError.
1199
+ * Planned for v0.2.0.
1200
+ * @defaultValue false
1201
+ */
1202
+ useCache?: boolean;
1203
+ /**
1204
+ * Cache directory for metadata cache.
1205
+ * @alpha Not yet implemented. Ignored until useCache is implemented.
1206
+ * Planned for v0.2.0.
1207
+ * @defaultValue ".xrmforge/cache"
1208
+ */
1209
+ cacheDir?: string;
1210
+ /** XrmForge namespace prefix (default: "XrmForge") */
1211
+ namespacePrefix?: string;
1212
+ }
1213
+ /** Result of generating types for a single entity */
1214
+ interface EntityGenerationResult {
1215
+ /** Entity logical name */
1216
+ entityLogicalName: string;
1217
+ /** Files written */
1218
+ files: GeneratedFile[];
1219
+ /** Warnings (e.g. missing labels, empty forms) */
1220
+ warnings: string[];
1221
+ }
1222
+ /** A single generated file */
1223
+ interface GeneratedFile {
1224
+ /** Relative path from outputDir */
1225
+ relativePath: string;
1226
+ /** File content */
1227
+ content: string;
1228
+ /** Type of generated content */
1229
+ type: 'entity' | 'optionset' | 'form';
1230
+ }
1231
+ /** Overall result of the generation process */
1232
+ interface GenerationResult {
1233
+ /** Per-entity results */
1234
+ entities: EntityGenerationResult[];
1235
+ /** Total files written */
1236
+ totalFiles: number;
1237
+ /** Total warnings */
1238
+ totalWarnings: number;
1239
+ /** Duration in milliseconds */
1240
+ durationMs: number;
1241
+ }
1242
+
1243
+ /**
1244
+ * @xrmforge/typegen - Type Generation Orchestrator
1245
+ *
1246
+ * Coordinates the full type generation pipeline:
1247
+ * 1. Fetch metadata for requested entities (via MetadataClient)
1248
+ * 2. Generate entity interfaces, OptionSet enums, form interfaces
1249
+ * 3. Write .d.ts files to disk
1250
+ *
1251
+ * This is the main entry point that ties all components together.
1252
+ */
1253
+
1254
+ /**
1255
+ * Main orchestrator for type generation.
1256
+ *
1257
+ * Usage:
1258
+ * ```typescript
1259
+ * const orchestrator = new TypeGenerationOrchestrator(credential, {
1260
+ * environmentUrl: 'https://myorg.crm4.dynamics.com',
1261
+ * entities: ['account', 'contact'],
1262
+ * outputDir: './typings',
1263
+ * labelConfig: { primaryLanguage: 1033, secondaryLanguage: 1031 },
1264
+ * });
1265
+ * const result = await orchestrator.generate();
1266
+ * ```
1267
+ */
1268
+ declare class TypeGenerationOrchestrator {
1269
+ private readonly config;
1270
+ private readonly credential;
1271
+ private readonly logger;
1272
+ constructor(credential: TokenCredential, config: GenerateConfig, logger?: Logger);
1273
+ /**
1274
+ * Run the full type generation pipeline.
1275
+ *
1276
+ * @param options - Optional parameters
1277
+ * @param options.signal - AbortSignal to cancel the generation process.
1278
+ * When aborted, entities that have not yet started processing are skipped.
1279
+ * Entities already in progress may still complete or fail with an abort error.
1280
+ */
1281
+ generate(options?: {
1282
+ signal?: AbortSignal;
1283
+ }): Promise<GenerationResult>;
1284
+ /**
1285
+ * Process a single entity: fetch metadata, generate all output files.
1286
+ */
1287
+ private processEntity;
1288
+ /**
1289
+ * Extract picklist attributes with their OptionSet metadata.
1290
+ * Maps the raw EntityTypeInfo data to the format expected by the OptionSet generator.
1291
+ */
1292
+ private getPicklistAttributes;
1293
+ }
1294
+
1295
+ export { ApiRequestError, type AttributeMetadata, type AuthConfig, type AuthMethod, AuthenticationError, type ClientCredentialsAuth, ConfigError, ConsoleLogSink, DEFAULT_LABEL_CONFIG, DataverseHttpClient, type DateTimeAttributeMetadata, type DecimalAttributeMetadata, type DeviceCodeAuth, type EntityGenerationResult, type EntityGeneratorOptions, type EntityMetadata, type EntityTypeInfo, ErrorCode, FastXmlParser, type FormControl, type FormGeneratorOptions, type FormSection, type FormTab, type GenerateConfig, type GeneratedFile, GenerationError, type GenerationResult, type HttpClientOptions, type IntegerAttributeMetadata, type InteractiveAuth, JsonLogSink, type Label, type LabelConfig, type LocalizedLabel, type LogEntry, LogLevel, type LogSink, Logger, type LookupAttributeMetadata, type ManyToManyRelationshipMetadata, MetadataCache, MetadataClient, MetadataError, type MoneyAttributeMetadata, type OneToManyRelationshipMetadata, type OptionMetadata, type OptionSetGeneratorOptions, type OptionSetMetadata, type ParsedForm, type PicklistAttributeMetadata, SilentLogSink, type SolutionComponent, type StateAttributeMetadata, type StatusAttributeMetadata, type StringAttributeMetadata, type SystemFormMetadata, TypeGenerationOrchestrator, type XmlElement, type XmlParser, XrmForgeError, configureLogging, createCredential, createLogger, defaultXmlParser, disambiguateEnumMembers, extractControlFields, getJSDocLabel as formatDualLabel, generateEntityForms, generateEntityInterface, generateEntityOptionSets, generateEnumMembers, generateFormInterface, generateOptionSetEnum, getEntityPropertyType, getFormAttributeType, getFormControlType, getJSDocLabel, getLabelLanguagesParam, getPrimaryLabel, getSecondaryLabel, isLookupType, isRateLimitError, isXrmForgeError, labelToIdentifier, parseForm, shouldIncludeInEntityInterface, toLookupValueProperty, toPascalCase, toSafeIdentifier };