@xrmforge/devkit 0.7.29 → 0.7.30
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.
package/dist/templates/AGENT.md
CHANGED
|
@@ -305,6 +305,13 @@ export const onLoad = wrapHandler('Namespace.Entity.onLoad', logger, async (ctx)
|
|
|
305
305
|
});
|
|
306
306
|
```
|
|
307
307
|
|
|
308
|
+
Ribbon/command bar commands use `wrapCommand` (it receives a FormContext, not an
|
|
309
|
+
EventContext). Pass extra command parameters through its `TArgs` type parameter
|
|
310
|
+
instead of widening to `any`. A command registered on a **subgrid** receives a
|
|
311
|
+
`GridControl` as PrimaryControl (which has no form `ui`), so wrap it with
|
|
312
|
+
`wrapGridCommand` - its error surface is an app-level banner, and its default extra
|
|
313
|
+
argument is the selected record ids (`string[]`).
|
|
314
|
+
|
|
308
315
|
### 8. Custom API Executors from generated/actions/
|
|
309
316
|
|
|
310
317
|
Never build your own ExecuteFunctionCall wrapper. Use the generated executors.
|
|
@@ -515,8 +522,13 @@ export function createLogger(namespace: string): Logger;
|
|
|
515
522
|
### error-handler.ts
|
|
516
523
|
```typescript
|
|
517
524
|
export function wrapHandler(name: string, logger: Logger, handler: EventHandler): EventHandler;
|
|
518
|
-
|
|
519
|
-
|
|
525
|
+
// Ribbon/command bar command on a form. Pass extra command parameters through TArgs (default none).
|
|
526
|
+
export function wrapCommand<TArgs extends unknown[] = []>(name, logger, handler): (formContext, ...args) => unknown;
|
|
527
|
+
// Command registered on a SUBGRID: PrimaryControl may be a GridControl (no form ui).
|
|
528
|
+
// Default extra arg is the selected record ids (string[]).
|
|
529
|
+
export function wrapGridCommand<TArgs extends unknown[] = [string[]]>(name, logger, handler): (primaryControl, ...args) => unknown;
|
|
530
|
+
// Catches sync+async errors. wrapHandler/wrapCommand show a form notification via
|
|
531
|
+
// FormNotificationLevel.Error; wrapGridCommand uses an app-level banner (GridControl has no form ui).
|
|
520
532
|
```
|
|
521
533
|
|
|
522
534
|
### constants.ts
|
|
@@ -850,7 +862,7 @@ regenerate and commit.
|
|
|
850
862
|
```
|
|
851
863
|
src/forms/{entity}-form.ts - Form scripts (one per entity)
|
|
852
864
|
src/shared/logger.ts - Structured logger (only file with console.*)
|
|
853
|
-
src/shared/error-handler.ts - wrapHandler + wrapCommand
|
|
865
|
+
src/shared/error-handler.ts - wrapHandler + wrapCommand + wrapGridCommand
|
|
854
866
|
src/shared/constants.ts - NOTIFICATION_IDS, MESSAGES, pickLang
|
|
855
867
|
generated/ - Generated types (do not edit manually)
|
|
856
868
|
tests/forms/{entity}.test.ts - Tests
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Unified error handling for D365 form event handlers.
|
|
3
|
-
* Wraps sync and async handlers with try/catch and form
|
|
2
|
+
* Unified error handling for D365 form event handlers and ribbon commands.
|
|
3
|
+
* Wraps sync and async handlers with try/catch: form handlers and form commands
|
|
4
|
+
* show a form notification, subgrid commands an app-level notification banner.
|
|
4
5
|
*/
|
|
5
6
|
import type { Logger } from './logger.js';
|
|
6
7
|
import { NOTIFICATION_IDS } from './constants.js';
|
|
7
|
-
import { FormNotificationLevel } from '@xrmforge/helpers';
|
|
8
|
+
import { FormNotificationLevel, AppNotificationLevel, addAppNotification } from '@xrmforge/helpers';
|
|
8
9
|
|
|
9
10
|
type EventHandler = (ctx: Xrm.Events.EventContext, ...args: never[]) => unknown;
|
|
10
11
|
|
|
@@ -36,20 +37,28 @@ export function wrapHandler(name: string, logger: Logger, handler: EventHandler)
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
/**
|
|
39
|
-
* Wrap a ribbon command handler with error handling.
|
|
40
|
+
* Wrap a ribbon command handler (form context) with error handling.
|
|
40
41
|
*
|
|
41
42
|
* Unlike wrapHandler, this accepts a FormContext directly (not an EventContext),
|
|
42
43
|
* which is the calling convention for ribbon/command bar handlers.
|
|
43
44
|
*
|
|
45
|
+
* Pass extra ribbon command parameters via the TArgs type parameter so they stay
|
|
46
|
+
* typed end to end (e.g. `wrapCommand<[boolean]>(...)` for a handler that takes a
|
|
47
|
+
* flag after the form context). TArgs defaults to `[]` (no extra parameters).
|
|
48
|
+
*
|
|
49
|
+
* For commands registered on a subgrid (the PrimaryControl may be a GridControl,
|
|
50
|
+
* not a FormContext) use {@link wrapGridCommand} instead.
|
|
51
|
+
*
|
|
52
|
+
* @typeParam TArgs - Tuple of extra parameters passed after the form context
|
|
44
53
|
* @param name - Handler name for logging
|
|
45
54
|
* @param logger - Logger instance for error reporting
|
|
46
55
|
* @param handler - The actual command handler function
|
|
47
56
|
*/
|
|
48
|
-
export function wrapCommand(
|
|
57
|
+
export function wrapCommand<TArgs extends unknown[] = []>(
|
|
49
58
|
name: string,
|
|
50
59
|
logger: Logger,
|
|
51
|
-
handler: (formContext: Xrm.FormContext, ...args:
|
|
52
|
-
): (formContext: Xrm.FormContext, ...args:
|
|
60
|
+
handler: (formContext: Xrm.FormContext, ...args: TArgs) => unknown,
|
|
61
|
+
): (formContext: Xrm.FormContext, ...args: TArgs) => unknown {
|
|
53
62
|
return (formContext, ...args) => {
|
|
54
63
|
try {
|
|
55
64
|
const result = handler(formContext, ...args);
|
|
@@ -65,6 +74,43 @@ export function wrapCommand(
|
|
|
65
74
|
};
|
|
66
75
|
}
|
|
67
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Wrap a ribbon command handler whose PrimaryControl can be a subgrid.
|
|
79
|
+
*
|
|
80
|
+
* Commands registered on a subgrid (or on both a form and a subgrid) receive a
|
|
81
|
+
* `GridControl` as the first argument when fired from the grid, plus the selected
|
|
82
|
+
* record ids. A `GridControl` has no form `ui`, so a form notification cannot be
|
|
83
|
+
* shown - this variant reports errors via the logger and an app-level banner
|
|
84
|
+
* ({@link addAppNotification}) that works independently of the form context.
|
|
85
|
+
*
|
|
86
|
+
* TArgs defaults to `[string[]]` (the selected record ids that D365 passes as the
|
|
87
|
+
* SelectedControlSelectedItemIds command parameter).
|
|
88
|
+
*
|
|
89
|
+
* @typeParam TArgs - Tuple of extra parameters passed after the primary control
|
|
90
|
+
* @param name - Handler name for logging
|
|
91
|
+
* @param logger - Logger instance for error reporting
|
|
92
|
+
* @param handler - The actual command handler function
|
|
93
|
+
*/
|
|
94
|
+
export function wrapGridCommand<TArgs extends unknown[] = [string[]]>(
|
|
95
|
+
name: string,
|
|
96
|
+
logger: Logger,
|
|
97
|
+
handler: (primaryControl: Xrm.FormContext | Xrm.Controls.GridControl, ...args: TArgs) => unknown,
|
|
98
|
+
): (primaryControl: Xrm.FormContext | Xrm.Controls.GridControl, ...args: TArgs) => unknown {
|
|
99
|
+
return (primaryControl, ...args) => {
|
|
100
|
+
try {
|
|
101
|
+
const result = handler(primaryControl, ...args);
|
|
102
|
+
if (result && typeof (result as Promise<unknown>).then === 'function') {
|
|
103
|
+
return (result as Promise<unknown>).catch((err: unknown) => {
|
|
104
|
+
logAndNotifyApp(name, logger, err);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
} catch (err: unknown) {
|
|
109
|
+
logAndNotifyApp(name, logger, err);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
68
114
|
function logAndNotify(
|
|
69
115
|
ctx: Xrm.Events.EventContext,
|
|
70
116
|
name: string,
|
|
@@ -94,3 +140,19 @@ function logAndNotifyForm(
|
|
|
94
140
|
/* ignore */
|
|
95
141
|
}
|
|
96
142
|
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Log an error and surface it as an app-level (global) notification banner.
|
|
146
|
+
*
|
|
147
|
+
* Used by {@link wrapGridCommand}: the PrimaryControl may be a GridControl that
|
|
148
|
+
* has no form `ui`, so a form notification is not available. The app banner is
|
|
149
|
+
* shown regardless of the calling control. The async banner call is
|
|
150
|
+
* fire-and-forget so the command handler is not forced to be async.
|
|
151
|
+
*/
|
|
152
|
+
function logAndNotifyApp(name: string, logger: Logger, err: unknown): void {
|
|
153
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
154
|
+
logger.error(`${name} failed`, { err });
|
|
155
|
+
void addAppNotification(message, AppNotificationLevel.Error).catch(() => {
|
|
156
|
+
/* ignore */
|
|
157
|
+
});
|
|
158
|
+
}
|