@khester/create-dynamics-app 2.0.0 → 2.2.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 (122) hide show
  1. package/README.md +28 -0
  2. package/dist/artifacts/registry.d.ts +4 -3
  3. package/dist/artifacts/registry.d.ts.map +1 -1
  4. package/dist/artifacts/registry.js +145 -11
  5. package/dist/artifacts/registry.js.map +1 -1
  6. package/dist/artifacts/types.d.ts +10 -1
  7. package/dist/artifacts/types.d.ts.map +1 -1
  8. package/dist/index.js +19 -2
  9. package/dist/index.js.map +1 -1
  10. package/dist/injectDevTools.d.ts.map +1 -1
  11. package/dist/injectDevTools.js +4 -2
  12. package/dist/injectDevTools.js.map +1 -1
  13. package/dist/scaffold.d.ts +23 -1
  14. package/dist/scaffold.d.ts.map +1 -1
  15. package/dist/scaffold.js +27 -1
  16. package/dist/scaffold.js.map +1 -1
  17. package/package.json +3 -2
  18. package/templates/grid-starter/ARCHITECTURE.md +66 -0
  19. package/templates/grid-starter/README.md +122 -0
  20. package/templates/grid-starter/env.example +16 -0
  21. package/templates/grid-starter/gitignore +6 -0
  22. package/templates/grid-starter/index.html +16 -0
  23. package/templates/grid-starter/package.json +39 -0
  24. package/templates/grid-starter/src/App.tsx +23 -0
  25. package/templates/grid-starter/src/core/services/FetchApiService.ts +117 -0
  26. package/templates/grid-starter/src/core/services/IApiService.ts +37 -0
  27. package/templates/grid-starter/src/core/services/MockApiService.ts +72 -0
  28. package/templates/grid-starter/src/core/services/ServiceFactory.ts +58 -0
  29. package/templates/grid-starter/src/core/services/XrmApiService.ts +135 -0
  30. package/templates/grid-starter/src/core/services/crudLogging.ts +52 -0
  31. package/templates/grid-starter/src/dev-tools/DevPanel.tsx +239 -0
  32. package/templates/grid-starter/src/grid/GridPage.tsx +119 -0
  33. package/templates/grid-starter/src/index.tsx +18 -0
  34. package/templates/grid-starter/src/vite-env.d.ts +15 -0
  35. package/templates/grid-starter/tools/deploy/deploy-webresource.cjs +117 -0
  36. package/templates/grid-starter/tsconfig.json +19 -0
  37. package/templates/grid-starter/vite.config.ts +76 -0
  38. package/templates/pcf-field/_variants/ValueInput.boolean.tsx +2 -0
  39. package/templates/pcf-field/_variants/ValueInput.date.tsx +2 -0
  40. package/templates/pcf-field/_variants/ValueInput.number.tsx +2 -0
  41. package/templates/pcf-field/_variants/ValueInput.optionset.tsx +77 -0
  42. package/templates/pcf-field/_variants/ValueInput.text.tsx +2 -0
  43. package/templates/pcf-field/index.ts +1 -1
  44. package/templates/pcf-field/package.json +3 -1
  45. package/templates/pcf-field/{{componentName}}Component.tsx +2 -0
  46. package/templates/react-custom-page/ARCHITECTURE.md +75 -0
  47. package/templates/react-custom-page/README.md +74 -568
  48. package/templates/react-custom-page/env.example +16 -0
  49. package/templates/react-custom-page/gitignore +1 -0
  50. package/templates/react-custom-page/index.html +16 -0
  51. package/templates/react-custom-page/package.json +21 -49
  52. package/templates/react-custom-page/src/App.tsx +26 -0
  53. package/templates/react-custom-page/src/core/recordContext.test.ts +30 -0
  54. package/templates/react-custom-page/src/core/recordContext.ts +51 -0
  55. package/templates/react-custom-page/src/core/services/FetchApiService.ts +117 -0
  56. package/templates/react-custom-page/src/core/services/IApiService.ts +37 -0
  57. package/templates/react-custom-page/src/core/services/MockApiService.ts +73 -0
  58. package/templates/react-custom-page/src/core/services/ServiceFactory.ts +58 -0
  59. package/templates/react-custom-page/src/core/services/XrmApiService.ts +135 -0
  60. package/templates/react-custom-page/src/core/services/crudLogging.ts +52 -0
  61. package/templates/react-custom-page/src/dev-tools/DevPanel.tsx +238 -0
  62. package/templates/react-custom-page/src/domain/diff.test.ts +87 -0
  63. package/templates/react-custom-page/src/domain/diff.ts +38 -0
  64. package/templates/react-custom-page/src/example/ExamplePage.tsx +140 -0
  65. package/templates/react-custom-page/src/example/exampleError.ts +36 -0
  66. package/templates/react-custom-page/src/example/hooks/useExampleData.ts +40 -0
  67. package/templates/react-custom-page/src/example/hooks/useExampleForm.ts +99 -0
  68. package/templates/react-custom-page/src/example/mappers/accountMapper.test.ts +38 -0
  69. package/templates/react-custom-page/src/example/mappers/accountMapper.ts +55 -0
  70. package/templates/react-custom-page/src/example/models/Account.ts +74 -0
  71. package/templates/react-custom-page/src/index.tsx +18 -128
  72. package/templates/react-custom-page/src/vite-env.d.ts +15 -0
  73. package/templates/react-custom-page/tools/deploy/deploy-webresource.cjs +117 -0
  74. package/templates/react-custom-page/tsconfig.json +12 -22
  75. package/templates/react-custom-page/vite.config.ts +76 -0
  76. package/templates/starter-page/README.md +38 -0
  77. package/templates/starter-page/_variants/App.dashboard.v8.tsx +46 -0
  78. package/templates/starter-page/_variants/App.form.v8.tsx +59 -0
  79. package/templates/starter-page/_variants/App.master-detail.v8.tsx +61 -0
  80. package/templates/starter-page/_variants/App.panel.v8.tsx +99 -0
  81. package/templates/starter-page/gitignore +5 -0
  82. package/templates/starter-page/package.json +27 -0
  83. package/templates/starter-page/public/index.html +11 -0
  84. package/templates/starter-page/src/index.tsx +10 -0
  85. package/templates/starter-page/src/services/dataverse.ts +30 -0
  86. package/templates/starter-page/tsconfig.json +15 -0
  87. package/templates/starter-page/webpack.config.js +17 -0
  88. package/templates/react-custom-page/deployment/README.md +0 -484
  89. package/templates/react-custom-page/docs/ARCHITECTURE_OVERVIEW.md +0 -506
  90. package/templates/react-custom-page/docs/BEST_PRACTICES.md +0 -723
  91. package/templates/react-custom-page/docs/MIGRATION_GUIDE.md +0 -447
  92. package/templates/react-custom-page/public/index.html +0 -15
  93. package/templates/react-custom-page/scripts/custom-build.js +0 -255
  94. package/templates/react-custom-page/src/components/AccountForm.css +0 -71
  95. package/templates/react-custom-page/src/components/AccountForm.tsx +0 -541
  96. package/templates/react-custom-page/src/components/AccountManagement.css +0 -86
  97. package/templates/react-custom-page/src/components/AccountManagement.tsx +0 -370
  98. package/templates/react-custom-page/src/components/ContactForm.css +0 -48
  99. package/templates/react-custom-page/src/components/ContactForm.tsx +0 -327
  100. package/templates/react-custom-page/src/components/ContactManagement.css +0 -86
  101. package/templates/react-custom-page/src/components/ContactManagement.tsx +0 -357
  102. package/templates/react-custom-page/src/components/Logging/LogDialog.tsx +0 -291
  103. package/templates/react-custom-page/src/components/Logging/LoggingContext.tsx +0 -166
  104. package/templates/react-custom-page/src/components/Logging/LoggingDebugPanel.css +0 -192
  105. package/templates/react-custom-page/src/components/Logging/LoggingDebugPanel.tsx +0 -177
  106. package/templates/react-custom-page/src/components/Logging/LoggingProvider.tsx +0 -3
  107. package/templates/react-custom-page/src/components/Logging/logger.ts +0 -193
  108. package/templates/react-custom-page/src/constants/account.ts +0 -410
  109. package/templates/react-custom-page/src/constants/contact.ts +0 -362
  110. package/templates/react-custom-page/src/models/Account.ts +0 -480
  111. package/templates/react-custom-page/src/models/BaseEntity.ts +0 -204
  112. package/templates/react-custom-page/src/models/Contact.ts +0 -580
  113. package/templates/react-custom-page/src/pcf/ContactControlWrapper.tsx +0 -107
  114. package/templates/react-custom-page/src/pcf/MultiEntityControlWrapper.tsx +0 -205
  115. package/templates/react-custom-page/src/providers/DynamicsProvider.tsx +0 -353
  116. package/templates/react-custom-page/src/services/MockApiService.ts +0 -260
  117. package/templates/react-custom-page/src/services/ServiceFactory.ts +0 -65
  118. package/templates/react-custom-page/src/services/XrmApiService.ts +0 -213
  119. package/templates/react-custom-page/src/styles/index.css +0 -171
  120. package/templates/react-custom-page/tools/metadata-sync/index.js +0 -152
  121. package/templates/react-custom-page/webpack.config.js +0 -57
  122. /package/templates/_shared/dev-tools/auth/{get-token.js → get-token.cjs} +0 -0
@@ -1,723 +0,0 @@
1
- # Best Practices - Dynamics 365 Template
2
-
3
- ## Overview
4
-
5
- This document outlines best practices for developing robust Dynamics 365 applications using the
6
- enhanced template patterns.
7
-
8
- ## Architecture Best Practices
9
-
10
- ### 1. Entity Model Design
11
-
12
- #### ✅ DO: Use Static Methods for CRUD Operations
13
-
14
- ```typescript
15
- export class Account extends BaseEntity {
16
- public static async create(apiService: IApiService, account: Account): Promise<Account> {
17
- return await this.createEntity<Account>(
18
- apiService,
19
- account,
20
- AccountConstants.EntityCollectionName,
21
- 'Account.create'
22
- );
23
- }
24
-
25
- public static async retrieveByName(apiService: IApiService, name: string): Promise<Account[]> {
26
- const fetchXml = this.buildFetchXml(
27
- AccountConstants.EntityLogicalName,
28
- ['accountid', 'name', 'emailaddress1'],
29
- `<filter type="and">
30
- <condition attribute="name" operator="like" value="%${this.escapeXml(name)}%" />
31
- </filter>`
32
- );
33
-
34
- return await this.retrieveEntitiesByFilter<Account>(
35
- apiService,
36
- AccountConstants.EntityCollectionName,
37
- fetchXml,
38
- Account,
39
- 'Account.retrieveByName'
40
- );
41
- }
42
- }
43
- ```
44
-
45
- #### ❌ DON'T: Mix Instance and Static Methods
46
-
47
- ```typescript
48
- // Avoid this pattern
49
- export class Account {
50
- public async save() {
51
- /* instance method */
52
- }
53
- public static async create() {
54
- /* static method */
55
- }
56
- public async delete() {
57
- /* instance method */
58
- }
59
- }
60
- ```
61
-
62
- #### ✅ DO: Implement Comprehensive Validation
63
-
64
- ```typescript
65
- export class Account extends BaseEntity {
66
- public validate(): boolean {
67
- const errors: string[] = [];
68
-
69
- // Required field validation
70
- if (!this.name?.trim()) {
71
- errors.push('Account name is required');
72
- }
73
-
74
- // Length validation
75
- if (this.name && this.name.length > 160) {
76
- errors.push('Account name cannot exceed 160 characters');
77
- }
78
-
79
- // Format validation
80
- if (this.emailaddress1 && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.emailaddress1)) {
81
- errors.push('Invalid email format');
82
- }
83
-
84
- // Business rule validation
85
- if (this.revenue && this.revenue < 0) {
86
- errors.push('Revenue cannot be negative');
87
- }
88
-
89
- if (errors.length > 0) {
90
- Logger.validation('Account', errors, 'Account.validate');
91
- throw new Error(`Validation failed: ${errors.join(', ')}`);
92
- }
93
-
94
- return true;
95
- }
96
- }
97
- ```
98
-
99
- ### 2. Service Layer Architecture
100
-
101
- #### ✅ DO: Use the Service Factory Pattern
102
-
103
- ```typescript
104
- // Let the factory determine the appropriate service
105
- const apiService = ServiceFactory.createApiService();
106
-
107
- // Environment-aware service creation
108
- const apiService = ServiceFactory.createApiService(
109
- ServiceFactory.isDynamics365Context() ? (window as any).Xrm : undefined
110
- );
111
- ```
112
-
113
- #### ❌ DON'T: Hard-code Service Selection
114
-
115
- ```typescript
116
- // Avoid hard-coding environment detection
117
- const apiService =
118
- window.location.hostname === 'localhost'
119
- ? new MockApiService()
120
- : new XrmApiService((window as any).Xrm);
121
- ```
122
-
123
- #### ✅ DO: Implement Proper Error Handling in Services
124
-
125
- ```typescript
126
- export class XrmApiService implements IApiService {
127
- async createRecord(entityName: string, record: any): Promise<any> {
128
- try {
129
- if (!this.xrm?.WebApi) {
130
- throw new Error('Xrm.WebApi is not available');
131
- }
132
-
133
- const result = await this.xrm.WebApi.createRecord(entityName, record);
134
- Logger.apiOperation('CREATE', entityName, record, 'XrmApiService.createRecord');
135
- return result;
136
- } catch (error) {
137
- Logger.error(`Failed to create ${entityName} record`, 'XrmApiService.createRecord', error);
138
- throw new Error(`Unable to create ${entityName}: ${error}`);
139
- }
140
- }
141
- }
142
- ```
143
-
144
- ### 3. Component Architecture
145
-
146
- #### ✅ DO: Use the Provider Pattern for API Services
147
-
148
- ```typescript
149
- function App() {
150
- return (
151
- <DynamicsProvider>
152
- <LoggingProvider>
153
- <Router>
154
- <Routes>
155
- <Route path="/accounts" element={<AccountManagement />} />
156
- <Route path="/contacts" element={<ContactManagement />} />
157
- </Routes>
158
- </Router>
159
- </LoggingProvider>
160
- </DynamicsProvider>
161
- );
162
- }
163
-
164
- function AccountManagement() {
165
- const { apiService } = useDynamicsApi(); // Gets service from context
166
- // Component implementation
167
- }
168
- ```
169
-
170
- #### ✅ DO: Implement Proper Loading and Error States
171
-
172
- ```typescript
173
- export const AccountManagement: React.FC = () => {
174
- const [accounts, setAccounts] = useState<Account[]>([]);
175
- const [loading, setLoading] = useState(false);
176
- const [error, setError] = useState<string | null>(null);
177
-
178
- const loadAccounts = useCallback(async () => {
179
- setLoading(true);
180
- setError(null);
181
-
182
- try {
183
- const accountsData = await Account.retrieveActiveAccounts(apiService);
184
- setAccounts(accountsData);
185
- Logger.log(`Loaded ${accountsData.length} accounts`, 'AccountManagement.loadAccounts');
186
- } catch (error) {
187
- const errorMessage = 'Failed to load accounts';
188
- setError(errorMessage);
189
- Logger.error(errorMessage, 'AccountManagement.loadAccounts', error);
190
- } finally {
191
- setLoading(false);
192
- }
193
- }, [apiService]);
194
-
195
- if (loading) return <div>Loading accounts...</div>;
196
- if (error) return <div>Error: {error}</div>;
197
- if (accounts.length === 0) return <div>No accounts found</div>;
198
-
199
- return (
200
- <DetailsList
201
- items={accounts}
202
- columns={columns}
203
- onItemInvoked={handleItemInvoked}
204
- />
205
- );
206
- };
207
- ```
208
-
209
- #### ❌ DON'T: Ignore Error States
210
-
211
- ```typescript
212
- // Avoid this - no error handling
213
- export const AccountManagement: React.FC = () => {
214
- const [accounts, setAccounts] = useState<Account[]>([]);
215
-
216
- useEffect(() => {
217
- Account.retrieveActiveAccounts(apiService)
218
- .then(setAccounts); // No error handling
219
- }, []);
220
-
221
- return <DetailsList items={accounts} columns={columns} />;
222
- };
223
- ```
224
-
225
- ## Development Best Practices
226
-
227
- ### 1. Logging Strategy
228
-
229
- #### ✅ DO: Use Structured Logging
230
-
231
- ```typescript
232
- // User actions
233
- Logger.userAction(
234
- 'Account created',
235
- { accountId: result.accountid, accountName: result.name },
236
- 'AccountForm.handleSubmit'
237
- );
238
-
239
- // API operations
240
- Logger.apiOperation('CREATE', 'account', accountData, 'Account.create');
241
-
242
- // Business logic
243
- Logger.log('Processing high-value opportunity', 'SalesWorkflow.applyBusinessRules');
244
-
245
- // Errors with context
246
- Logger.error('Validation failed', 'AccountForm.validateForm', {
247
- error,
248
- formData,
249
- validationErrors,
250
- });
251
- ```
252
-
253
- #### ❌ DON'T: Use Console Directly
254
-
255
- ```typescript
256
- // Avoid direct console usage
257
- console.log('Account created'); // No context
258
- console.error(error); // No structured data
259
- ```
260
-
261
- #### ✅ DO: Use Appropriate Log Levels
262
-
263
- ```typescript
264
- // Development debugging
265
- Logger.debug('Form state updated', 'AccountForm.handleInputChange', formData);
266
-
267
- // User feedback
268
- Logger.userAction('Search performed', { searchTerm, resultCount }, 'AccountManagement');
269
-
270
- // Performance monitoring
271
- Logger.timing('Account list load', startTime, 'AccountManagement.loadAccounts');
272
-
273
- // Validation feedback
274
- Logger.validation('Account', errors, 'Account.validate');
275
-
276
- // Critical errors
277
- Logger.error('API service unavailable', 'ServiceFactory.createApiService', error);
278
- ```
279
-
280
- ### 2. Error Handling Patterns
281
-
282
- #### ✅ DO: Implement Layered Error Handling
283
-
284
- ```typescript
285
- // Entity Layer - Business logic errors
286
- export class Account extends BaseEntity {
287
- public static async create(apiService: IApiService, account: Account): Promise<Account> {
288
- try {
289
- account.validate(); // Throws validation errors
290
- return await this.createEntity<Account>(apiService, account, AccountConstants.EntityCollectionName);
291
- } catch (error) {
292
- Logger.error('Account creation failed', 'Account.create', error);
293
- throw error; // Re-throw for component handling
294
- }
295
- }
296
- }
297
-
298
- // Component Layer - User feedback
299
- export const AccountForm: React.FC = () => {
300
- const [submitError, setSubmitError] = useState<string>('');
301
-
302
- const handleSubmit = async () => {
303
- try {
304
- setSubmitError('');
305
- const account = new Account(formData);
306
- await Account.create(apiService, account);
307
- onSave?.(); // Success callback
308
- } catch (error) {
309
- if (error instanceof Error) {
310
- setSubmitError(error.message); // Show user-friendly message
311
- } else {
312
- setSubmitError('An unexpected error occurred');
313
- }
314
- Logger.error('Form submission failed', 'AccountForm.handleSubmit', error);
315
- }
316
- };
317
-
318
- return (
319
- <form onSubmit={handleSubmit}>
320
- {submitError && <div className="error-message">{submitError}</div>}
321
- {/* Form fields */}
322
- </form>
323
- );
324
- };
325
- ```
326
-
327
- #### ✅ DO: Provide User-Friendly Error Messages
328
-
329
- ```typescript
330
- const getErrorMessage = (error: unknown): string => {
331
- if (error instanceof Error) {
332
- // Handle specific error types
333
- if (error.message.includes('validation failed')) {
334
- return 'Please check your input and try again.';
335
- }
336
- if (error.message.includes('network')) {
337
- return 'Unable to connect to server. Please check your connection.';
338
- }
339
- if (error.message.includes('permission')) {
340
- return 'You do not have permission to perform this action.';
341
- }
342
- return error.message;
343
- }
344
- return 'An unexpected error occurred. Please try again.';
345
- };
346
- ```
347
-
348
- ### 3. Performance Optimization
349
-
350
- #### ✅ DO: Use React Performance Optimizations
351
-
352
- ```typescript
353
- // Memoize expensive calculations
354
- const filteredAccounts = useMemo(() => {
355
- return accounts.filter(account =>
356
- account.name?.toLowerCase().includes(searchText.toLowerCase())
357
- );
358
- }, [accounts, searchText]);
359
-
360
- // Memoize callbacks to prevent unnecessary re-renders
361
- const handleItemInvoked = useCallback((item: Account) => {
362
- Logger.userAction('Account selected', { accountId: item.accountid });
363
- setSelectedAccount(item);
364
- }, []);
365
-
366
- // Use React.memo for stable components
367
- export const AccountListItem = React.memo<{ account: Account }>((props) => {
368
- return <div>{props.account.name}</div>;
369
- });
370
- ```
371
-
372
- #### ✅ DO: Implement Efficient Data Loading
373
-
374
- ```typescript
375
- // Load only required fields
376
- const fetchXml = Account.buildFetchXml(
377
- AccountConstants.EntityLogicalName,
378
- ['accountid', 'name', 'emailaddress1'], // Only fields needed for list
379
- filter,
380
- { attribute: 'createdon', descending: true }
381
- );
382
-
383
- // Implement pagination for large datasets
384
- const loadAccountsPage = async (pageNumber: number, pageSize: number = 50) => {
385
- const fetchXml = `
386
- <fetch mapping="logical" count="${pageSize}" page="${pageNumber}">
387
- <entity name="account">
388
- <attribute name="accountid" />
389
- <attribute name="name" />
390
- <attribute name="emailaddress1" />
391
- <order attribute="createdon" descending="true" />
392
- </entity>
393
- </fetch>`;
394
-
395
- return await Account.retrieveEntitiesByFilter(apiService, 'accounts', fetchXml, Account);
396
- };
397
- ```
398
-
399
- ### 4. Type Safety
400
-
401
- #### ✅ DO: Use Strong Typing Throughout
402
-
403
- ```typescript
404
- // Define proper interfaces
405
- interface AccountFormData {
406
- name: string;
407
- emailaddress1: string;
408
- telephone1: string;
409
- revenue: number | null;
410
- industrycode: number;
411
- }
412
-
413
- // Use generic types with constraints
414
- export class BaseEntity {
415
- protected static async createEntity<T extends BaseEntity>(
416
- apiService: IApiService,
417
- entity: T,
418
- entityName: string,
419
- loggerContext?: string
420
- ): Promise<T> {
421
- // Implementation
422
- }
423
- }
424
-
425
- // Type component props properly
426
- interface AccountFormProps {
427
- accountId?: string;
428
- initialData?: Partial<AccountFormData>;
429
- onSave?: (account: Account) => void;
430
- onCancel?: () => void;
431
- }
432
- ```
433
-
434
- #### ❌ DON'T: Use 'any' Unless Necessary
435
-
436
- ```typescript
437
- // Avoid this - too permissive
438
- const handleSubmit = (data: any) => {
439
- // No type safety
440
- };
441
-
442
- // Better - use proper types
443
- const handleSubmit = (data: AccountFormData) => {
444
- // Full type safety
445
- };
446
- ```
447
-
448
- ## Deployment Best Practices
449
-
450
- ### 1. Build Optimization
451
-
452
- #### ✅ DO: Use Environment-Specific Builds
453
-
454
- ```typescript
455
- // Development build - unminified, with source maps
456
- "build:dev": "node scripts/custom-build.js --dev"
457
-
458
- // Production build - minified, optimized
459
- "build:prod": "node scripts/custom-build.js"
460
-
461
- // D365-specific build - single bundle, web resource ready
462
- "build:d365": "node scripts/custom-build.js"
463
- ```
464
-
465
- #### ✅ DO: Monitor Bundle Size
466
-
467
- ```typescript
468
- // Check deployment info after build
469
- const deploymentInfo = require('./dist/deployment-info.json');
470
- console.log(`Bundle size: ${deploymentInfo.performance.totalSize} bytes`);
471
-
472
- // Ensure size is under D365 limits
473
- if (deploymentInfo.performance.totalSize > 2000000) {
474
- // 2MB limit
475
- console.warn('Bundle size exceeds recommended limit for web resources');
476
- }
477
- ```
478
-
479
- ### 2. Web Resource Management
480
-
481
- #### ✅ DO: Use Consistent Naming Convention
482
-
483
- ```
484
- // Web Resource Names
485
- new_/scripts/accountmanagement_main.js
486
- new_/scripts/contactmanagement_main.js
487
- new_/styles/dynamics_ui_styles.css
488
- new_/pages/accountmanagement.html
489
- ```
490
-
491
- #### ✅ DO: Implement Proper Caching Strategy
492
-
493
- ```typescript
494
- // Add version numbers to web resources
495
- const CACHE_VERSION = '1.0.0';
496
-
497
- // Check for cached API services
498
- const getCachedApiService = () => {
499
- const cached = sessionStorage.getItem(`apiService_${CACHE_VERSION}`);
500
- return cached ? JSON.parse(cached) : null;
501
- };
502
- ```
503
-
504
- ### 3. Security Considerations
505
-
506
- #### ✅ DO: Validate User Permissions
507
-
508
- ```typescript
509
- export class AccountService {
510
- public static async create(apiService: IApiService, account: Account): Promise<Account> {
511
- // Check user permissions before operations
512
- if (!(await this.checkCreatePermission(apiService))) {
513
- throw new Error('Insufficient permissions to create accounts');
514
- }
515
-
516
- return await Account.create(apiService, account);
517
- }
518
-
519
- private static async checkCreatePermission(apiService: IApiService): Promise<boolean> {
520
- try {
521
- // Use D365 security context
522
- const userPrivileges = await apiService.executeRequest('RetrieveUserPrivileges', {});
523
- return userPrivileges.some((p) => p.Name === 'prvCreateAccount');
524
- } catch {
525
- return false; // Fail secure
526
- }
527
- }
528
- }
529
- ```
530
-
531
- #### ✅ DO: Sanitize User Input
532
-
533
- ```typescript
534
- export class BaseEntity {
535
- protected static escapeXml(value: string): string {
536
- if (!value) return value;
537
-
538
- return value
539
- .replace(/&/g, '&amp;')
540
- .replace(/</g, '&lt;')
541
- .replace(/>/g, '&gt;')
542
- .replace(/"/g, '&quot;')
543
- .replace(/'/g, '&apos;');
544
- }
545
-
546
- protected static validateInput(input: string, maxLength: number): string {
547
- if (!input) return '';
548
-
549
- // Remove potentially dangerous characters
550
- const cleaned = input.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
551
-
552
- // Enforce length limits
553
- return cleaned.substring(0, maxLength);
554
- }
555
- }
556
- ```
557
-
558
- ### 4. Testing Strategy
559
-
560
- #### ✅ DO: Test in Multiple Environments
561
-
562
- ```typescript
563
- // Environment-specific testing
564
- describe('Account Management', () => {
565
- beforeEach(() => {
566
- // Test with mock service
567
- const mockService = new MockApiService();
568
- render(
569
- <DynamicsProvider customApiService={mockService}>
570
- <AccountManagement />
571
- </DynamicsProvider>
572
- );
573
- });
574
-
575
- test('should create account successfully', async () => {
576
- // Test implementation
577
- });
578
-
579
- test('should handle validation errors', async () => {
580
- // Test error scenarios
581
- });
582
- });
583
-
584
- // Integration testing with real D365
585
- describe('Account Management - Integration', () => {
586
- beforeEach(() => {
587
- // Test with real Xrm service (in development environment)
588
- const xrmService = new XrmApiService((window as any).Xrm);
589
- render(
590
- <DynamicsProvider customApiService={xrmService}>
591
- <AccountManagement />
592
- </DynamicsProvider>
593
- );
594
- });
595
- });
596
- ```
597
-
598
- #### ✅ DO: Implement Quality Gates
599
-
600
- ```typescript
601
- // Pre-commit quality checks
602
- "precommit": "npm run quality"
603
- "quality": "npm run lint && npm run typecheck"
604
-
605
- // Pre-deployment validation
606
- "validate": "npm run quality && npm run build:prod"
607
- "prepublishOnly": "npm run validate"
608
- ```
609
-
610
- ## Maintenance Best Practices
611
-
612
- ### 1. Documentation
613
-
614
- #### ✅ DO: Maintain Comprehensive Documentation
615
-
616
- ````typescript
617
- /**
618
- * Creates a new account with full validation and business rule enforcement
619
- *
620
- * @param apiService - The API service instance for Dynamics 365 operations
621
- * @param account - The account instance to create
622
- * @returns Promise resolving to the created account with server-generated fields
623
- *
624
- * @throws {Error} When validation fails or API operation fails
625
- *
626
- * @example
627
- * ```typescript
628
- * const account = new Account({ name: 'ACME Corp', emailaddress1: 'info@acme.com' });
629
- * const created = await Account.create(apiService, account);
630
- * console.log('Created account:', created.accountid);
631
- * ```
632
- */
633
- public static async create(apiService: IApiService, account: Account): Promise<Account> {
634
- // Implementation
635
- }
636
- ````
637
-
638
- ### 2. Monitoring and Logging
639
-
640
- #### ✅ DO: Implement Production Monitoring
641
-
642
- ```typescript
643
- // Performance monitoring
644
- const performanceObserver = new PerformanceObserver((list) => {
645
- for (const entry of list.getEntries()) {
646
- if (entry.duration > 1000) {
647
- // Log slow operations
648
- Logger.performance('Slow operation detected', {
649
- name: entry.name,
650
- duration: entry.duration,
651
- context: 'ProductionMonitoring',
652
- });
653
- }
654
- }
655
- });
656
-
657
- // Error tracking with context
658
- window.addEventListener('error', (event) => {
659
- Logger.error('Unhandled error in production', 'GlobalErrorHandler', {
660
- message: event.error?.message,
661
- filename: event.filename,
662
- lineno: event.lineno,
663
- colno: event.colno,
664
- stack: event.error?.stack,
665
- });
666
- });
667
-
668
- // API failure tracking
669
- const trackApiFailures = (operation: string, entityName: string, error: Error) => {
670
- Logger.error(`API operation failed: ${operation}`, 'ApiFailureTracker', {
671
- operation,
672
- entityName,
673
- error: error.message,
674
- timestamp: new Date().toISOString(),
675
- userAgent: navigator.userAgent,
676
- });
677
- };
678
- ```
679
-
680
- ### 3. Version Management
681
-
682
- #### ✅ DO: Implement Semantic Versioning
683
-
684
- ```json
685
- {
686
- "name": "dynamics-365-app",
687
- "version": "1.2.3", // MAJOR.MINOR.PATCH
688
- "description": "Version changes tracked in CHANGELOG.md"
689
- }
690
- ```
691
-
692
- #### ✅ DO: Maintain Backward Compatibility
693
-
694
- ```typescript
695
- // Support both old and new API patterns
696
- export class Account extends BaseEntity {
697
- // New method (preferred)
698
- public static async retrieveByName(apiService: IApiService, name: string): Promise<Account[]> {
699
- // Implementation
700
- }
701
-
702
- // Legacy method (deprecated but supported)
703
- /** @deprecated Use retrieveByName instead */
704
- public static async findByName(apiService: IApiService, name: string): Promise<Account[]> {
705
- Logger.warn('findByName is deprecated, use retrieveByName', 'Account.findByName');
706
- return this.retrieveByName(apiService, name);
707
- }
708
- }
709
- ```
710
-
711
- ## Conclusion
712
-
713
- Following these best practices ensures that your Dynamics 365 applications are:
714
-
715
- - **Robust**: Comprehensive error handling and validation
716
- - **Maintainable**: Clean architecture and proper documentation
717
- - **Performant**: Optimized builds and efficient data loading
718
- - **Secure**: Input validation and permission checking
719
- - **Scalable**: Modular architecture that grows with your needs
720
- - **Type-Safe**: Full TypeScript support throughout
721
-
722
- Regular review and adherence to these practices will result in high-quality, enterprise-grade
723
- Dynamics 365 applications.