@xrmforge/devkit 0.7.18 → 0.7.23

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.
@@ -189,6 +189,45 @@ import { AccountNavigationProperties as AccountNav } from '../../generated/entit
189
189
  const parent = parseLookup(apiResponse, AccountNav.ParentAccountId);
190
190
  ```
191
191
 
192
+ ### 5b. Lookup fields: `Fields` enum is `_value`-form, `NavigationProperties` is blank
193
+
194
+ typegen emits TWO enums per entity for lookups, with DIFFERENT values. Picking the wrong one
195
+ compiles green but breaks at runtime (no tsc/eslint gate catches it):
196
+
197
+ | Enum | Value for a lookup (e.g. `transactioncurrencyid`) | Use for |
198
+ |---|---|---|
199
+ | `XxxFields` | `'_transactioncurrencyid_value'` (already `_value`-form) | `$select`, `$filter` |
200
+ | `XxxNavigationProperties` | `'transactioncurrencyid'` (blank) | `parseLookup`, `$expand`, `@odata.bind`, `$unsafe` (lookup) |
201
+
202
+ ```typescript
203
+ import { AccountFields } from '../../generated/fields/account.js';
204
+ import { AccountNavigationProperties as AccountNav } from '../../generated/entities/account.js';
205
+
206
+ // $select / $filter: the Fields value is ALREADY _value-form, use it directly
207
+ select(AccountFields.TransactionCurrencyId); // -> "_transactioncurrencyid_value"
208
+
209
+ // parseLookup: the NavigationProperties value (blank), NOT Fields
210
+ const currency = parseLookup(apiResponse, AccountNav.TransactionCurrencyId);
211
+
212
+ // BUG (F-LMA7-05): double _value wrap -> "__transactioncurrencyid_value_value" -> OData 400 at runtime
213
+ const key = `_${AccountFields.TransactionCurrencyId}_value`;
214
+ // BUG: parseLookup with a Fields value (already _value) -> key wrong -> always returns null
215
+ parseLookup(apiResponse, AccountFields.TransactionCurrencyId);
216
+ ```
217
+
218
+ - **`$select`/`$filter`:** use the `XxxFields` value DIRECTLY. NEVER wrap it again as
219
+ `` `_${XxxFields.Lookup}_value` `` - the Fields value is already complete.
220
+ - **`parseLookup(response, X)`:** `X` MUST be `XxxNavigationProperties.Lookup` (blank). parseLookup
221
+ builds the key itself as `_${nav}_value`; a `XxxFields` value double-wraps and always returns `null`.
222
+ - **`$unsafe()` on an off-form LOOKUP** uses `XxxNavigationProperties.Lookup` (blank) - it takes an
223
+ attribute logical name, not the `_value` Web API key. `XxxFields.Lookup` (already `_value`-form) is
224
+ not a valid attribute name and resolves to `null` at runtime. For a non-lookup off-form field,
225
+ `XxxFields` is correct (F-LMA7-06).
226
+ - **Never write a local `lookupValue(field)` helper** that puts `_${field}_value` around a `XxxFields`
227
+ value (F-LMA7-05). It is plain string concatenation - green at compile time, broken at runtime.
228
+ - **parseLookup needs the raw response** (`Record<string, unknown>`), not a value cast to a generated
229
+ Entity interface (no index signature). Keep the raw response for parseLookup, cast separately.
230
+
192
231
  ### 6. select(), $filter, $expand, $orderby with Fields Enums
193
232
 
194
233
  ALL OData query parts must use entity-level Fields Enums. No raw field name strings anywhere.
@@ -374,6 +413,8 @@ Xrm.Navigation.openForm({ entityName: EntityNames.Account, entityId: id }); //
374
413
  - Never access WebApi response properties with `as string` casts (use generated Entity interfaces)
375
414
  - Never `.getValue()[0].id` for lookups (use `formLookup`/`formLookupId`)
376
415
  - Never raw strings in `parseLookup()` (use NavigationProperties enum)
416
+ - Never pass a `XxxFields` value to `parseLookup()` (use `XxxNavigationProperties`; a `XxxFields` value is already `_value`-form, so parseLookup double-wraps the key and always returns `null`)
417
+ - Never wrap a `XxxFields` lookup value again as `` `_${XxxFields.X}_value` `` (it is already `_value`-form; double-wrap -> `__..._value_value` -> OData 400). Use the Fields value directly in `$select`/`$filter`; use `XxxNavigationProperties` for `parseLookup`/`$expand`/`@odata.bind`
377
418
  - Never raw strings in `$unsafe()` (use Entity-level Fields Enum: `form.$unsafe(AccountFields.X)`)
378
419
  - Never manual OData annotation access (`_value`, `@OData.Community.Display.V1.FormattedValue`, `@Microsoft.Dynamics.CRM.lookuplogicalname`). Use `parseLookup()` which extracts all three.
379
420
 
@@ -598,6 +639,7 @@ each attribute to its control. `mock.getControl(Fields.Name)` works out of the b
598
639
  | `Xrm.WebApi.retrieveRecord("account", id)` | `Xrm.WebApi.retrieveRecord(EntityNames.Account, id)` |
599
640
  | `"?$select=name,revenue"` | `select(AccountFields.Name, AccountFields.Revenue)` |
600
641
  | `value[0].id.replace("{","")` | `formLookupId(form.customerid)` |
642
+ | `` `_${field}_value` `` hand-built lookup key | `XxxFields.X` directly in `$select`/`$filter` (already `_value`); `XxxNavigationProperties.X` for `parseLookup` |
601
643
  | `ExecuteFunctionCall("name", ...)` | `import { Name } from '../../generated/actions/global.js'` |
602
644
  | `setFormNotification(msg, 'ERROR', id)` | `setFormNotification(msg, FormNotificationLevel.Error, id)` |
603
645
  | `getValue() === 595300000` | `form.statuscode.getValue() === StatusCode.Active` |
@@ -307,6 +307,26 @@ checkPattern(
307
307
  ['generated/', 'node_modules'],
308
308
  );
309
309
 
310
+ // ── Lookup Convention (Fields vs NavigationProperties, F-LMA7-05) ─────────────
311
+
312
+ // 3q2. Hand-built `_<field>_value` key (Fields enum is already _value-form, never wrap again).
313
+ // Compiles green (plain string concatenation) but produces __..._value_value -> OData 400 at runtime.
314
+ checkPattern(
315
+ 'Double _value wrap on a lookup (Fields enum is already _value-form, no _${...}_value)',
316
+ allSrcFiles,
317
+ /_\$\{[^}]*\}_value/,
318
+ ['generated/'],
319
+ );
320
+
321
+ // 3q3. parseLookup with a Fields enum value (must use the NavigationProperties enum).
322
+ // parseLookup builds the key itself as _${nav}_value; a Fields value (already _value) yields null.
323
+ checkPattern(
324
+ 'parseLookup with a Fields enum (use the NavigationProperties enum instead)',
325
+ allSrcFiles,
326
+ /parseLookup\s*\(\s*\w+\s*,\s*\w*Fields\b/,
327
+ ['generated/'],
328
+ );
329
+
310
330
  // ── Legacy Helper Wrappers ───────────────────────────────────────────────────
311
331
 
312
332
  // 3r. Forbidden legacy helper functions (must use typedForm + @xrmforge/helpers)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xrmforge/devkit",
3
- "version": "0.7.18",
3
+ "version": "0.7.23",
4
4
  "description": "Build orchestration and project tooling for Dynamics 365 WebResources",
5
5
  "keywords": [
6
6
  "dynamics-365",