@xrmforge/typegen 0.13.0 → 0.13.2
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/LICENSE +21 -21
- package/README.md +112 -0
- package/dist/index.d.ts +32 -6
- package/dist/index.js +62 -52
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 XrmForge Contributors
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 XrmForge Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# @xrmforge/typegen
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@xrmforge/typegen)
|
|
4
|
+
[](https://github.com/juergenbeck/XrmForge/blob/main/LICENSE)
|
|
5
|
+
|
|
6
|
+
**The type-generation engine of XrmForge.** Reads Dynamics 365 / Dataverse metadata and generates TypeScript declarations that *extend* `@types/xrm` (never replace it), so your generated types coexist with PCF controls and the rest of the Microsoft ecosystem.
|
|
7
|
+
|
|
8
|
+
> Most users do not call this package directly -- they use [`@xrmforge/cli`](https://www.npmjs.com/package/@xrmforge/cli) (`xrmforge generate`), which wraps this engine. Install `@xrmforge/typegen` directly only when you want to embed generation in your own Node.js tooling. For the full framework docs, see the [XrmForge repository](https://github.com/juergenbeck/XrmForge#readme).
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## What it generates
|
|
13
|
+
|
|
14
|
+
For each entity, typegen emits flat ES modules (`.ts` files) -- one file per concern, imported and processed by your bundler. No `declare namespace`, no ambient `.d.ts`.
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
generated/
|
|
18
|
+
entities/account.ts export interface Account { ... } // typed Web API response objects
|
|
19
|
+
fields/account.ts export const enum AccountFields // $select / $filter (already _value form)
|
|
20
|
+
export const enum AccountNavigationProperties // parseLookup / $expand / @odata.bind
|
|
21
|
+
optionsets/account.ts export const enum IndustryCode ... // every picklist/status/state field
|
|
22
|
+
forms/account.ts Union + maps + Fields enum + Form interface + FormTypeInfo + MockValues
|
|
23
|
+
actions/quote.ts export const WinQuote = createBoundAction(...) // typed Custom API executors
|
|
24
|
+
entity-names.ts export const enum EntityNames
|
|
25
|
+
index.ts barrel re-export
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Each generated artifact is a compile-time contract:
|
|
29
|
+
|
|
30
|
+
- **Entity interfaces** -- all attributes correctly typed (string, number, boolean, Lookup, OptionSet, DateTime).
|
|
31
|
+
- **Form interfaces** -- per-form `getAttribute()` / `getControl()` overloads. Only fields actually on the form are valid; no string fallback, no `any`.
|
|
32
|
+
- **OptionSet enums** -- `const enum`, inlined by TypeScript (zero runtime overhead).
|
|
33
|
+
- **Fields / NavigationProperties enums** -- type-safe `$select` and lookup navigation.
|
|
34
|
+
- **Action / Function executors** -- generated from Custom API metadata, with typed parameters and responses.
|
|
35
|
+
- **Dual-language JSDoc** -- `/** Account Name | Firmenname */` when `--secondary-language` is set.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Usage via the CLI (recommended)
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install --save-dev @xrmforge/cli @types/xrm
|
|
43
|
+
npx xrmforge generate --url https://myorg.crm4.dynamics.com --auth interactive \
|
|
44
|
+
--tenant-id YOUR_TENANT_ID --client-id 51f81489-12ee-4a9e-aaae-a2591f45987d \
|
|
45
|
+
--entities account,contact --output ./generated
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
See [`@xrmforge/cli`](https://www.npmjs.com/package/@xrmforge/cli) for every flag, authentication method, incremental caching (`--cache`), and drift detection (`--check`).
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Programmatic API
|
|
53
|
+
|
|
54
|
+
Install directly when embedding generation in your own tooling:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm install @xrmforge/typegen @types/xrm
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The high-level entry point is the orchestrator, which runs the full pipeline (authenticate, read metadata, generate, write). It takes a credential and a config:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { TypeGenerationOrchestrator, createCredential } from '@xrmforge/typegen';
|
|
64
|
+
|
|
65
|
+
// 1. Build a credential from an auth config
|
|
66
|
+
// (method: 'interactive' | 'client-credentials' | 'device-code' | 'token')
|
|
67
|
+
const credential = createCredential({
|
|
68
|
+
method: 'interactive',
|
|
69
|
+
tenantId: 'YOUR_TENANT_ID',
|
|
70
|
+
clientId: '51f81489-12ee-4a9e-aaae-a2591f45987d',
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// 2. Run the pipeline
|
|
74
|
+
const orchestrator = new TypeGenerationOrchestrator(credential, {
|
|
75
|
+
environmentUrl: 'https://myorg.crm4.dynamics.com',
|
|
76
|
+
entities: ['account', 'contact'],
|
|
77
|
+
outputDir: './generated',
|
|
78
|
+
labelConfig: { primaryLanguage: 1033, secondaryLanguage: 1031 },
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const result = await orchestrator.generate();
|
|
82
|
+
console.log(`Generated ${result.totalFiles} files`);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
For finer control, the building blocks are exported individually:
|
|
86
|
+
|
|
87
|
+
| Area | Exports |
|
|
88
|
+
|------|---------|
|
|
89
|
+
| Orchestration | `TypeGenerationOrchestrator`, types `GenerateConfig`, `GenerationResult`, `CheckResult`, `CheckFinding`, `CacheStats` |
|
|
90
|
+
| Authentication | `createCredential`, types `AuthConfig`, `ClientCredentialsAuth`, `InteractiveAuth`, `DeviceCodeAuth` |
|
|
91
|
+
| HTTP | `DataverseHttpClient` (ReadOnly-default, retry, rate-limit), type `HttpClientOptions` |
|
|
92
|
+
| Metadata | `MetadataClient`, `MetadataCache`, `ChangeDetector`, `parseForm`, plus rich metadata types (`EntityMetadata`, `AttributeMetadata`, `OptionSetMetadata`, `SystemFormMetadata`, ...) |
|
|
93
|
+
| Code generators | `generateEntityInterface`, `generateFormInterface`, `generateOptionSetEnum`, `generateEntityFieldsEnum`, `generateActionModule`, `generateEntityNamesEnum`, ... |
|
|
94
|
+
| Type mapping | `getEntityPropertyType`, `getFormAttributeType`, `toSafeIdentifier`, `toPascalCase`, `isLookupType`, ... |
|
|
95
|
+
| Logging | `Logger`, `ConsoleLogSink`, `JsonLogSink`, `SilentLogSink`, `LogLevel`, `configureLogging` |
|
|
96
|
+
| Errors | `XrmForgeError`, `AuthenticationError`, `ApiRequestError`, `MetadataError`, `GenerationError`, `ConfigError`, `isXrmForgeError`, `isRateLimitError` |
|
|
97
|
+
|
|
98
|
+
> **Node.js only.** This package pulls in `@azure/identity` and Node APIs. Do **not** import it in browser/form-script code -- use [`@xrmforge/helpers`](https://www.npmjs.com/package/@xrmforge/helpers) for the browser runtime. The `@xrmforge/eslint-plugin` rule `no-typegen-import` enforces this.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Peer dependency
|
|
103
|
+
|
|
104
|
+
`@types/xrm` (>= 9.0.0) -- the generated types build on top of it.
|
|
105
|
+
|
|
106
|
+
## Documentation
|
|
107
|
+
|
|
108
|
+
Full guide and generated-type patterns: [XrmForge on GitHub](https://github.com/juergenbeck/XrmForge#readme).
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
[MIT](https://github.com/juergenbeck/XrmForge/blob/main/LICENSE) (c) XrmForge Contributors.
|
package/dist/index.d.ts
CHANGED
|
@@ -1331,6 +1331,25 @@ declare function generateEntityOptionSets(picklistAttributes: Array<{
|
|
|
1331
1331
|
* ```
|
|
1332
1332
|
*/
|
|
1333
1333
|
|
|
1334
|
+
/**
|
|
1335
|
+
* Machine-readable metadata for one generated form, surfaced in form-mapping.json
|
|
1336
|
+
* so AI agents can pick the right form by its fields without parsing the generated
|
|
1337
|
+
* code (F-MAR7-04).
|
|
1338
|
+
*/
|
|
1339
|
+
interface FormGenerationMeta {
|
|
1340
|
+
/** Form display name (from FormXml) */
|
|
1341
|
+
formName: string;
|
|
1342
|
+
/** Generated interface name (e.g. AccountForm) */
|
|
1343
|
+
interfaceName: string;
|
|
1344
|
+
/** Generated Fields enum name (e.g. AccountFormFieldsEnum) */
|
|
1345
|
+
fieldsEnumName: string;
|
|
1346
|
+
/** Generated Tabs enum name, or '' if the form has no named tabs */
|
|
1347
|
+
tabsEnumName: string;
|
|
1348
|
+
/** Sorted logical names of the attributes this form binds to */
|
|
1349
|
+
fields: string[];
|
|
1350
|
+
/** True for a Main form (systemform_type 2), false for Quick Create etc. */
|
|
1351
|
+
isMain: boolean;
|
|
1352
|
+
}
|
|
1334
1353
|
/** Options for form interface generation */
|
|
1335
1354
|
interface FormGeneratorOptions {
|
|
1336
1355
|
/** Label configuration for dual-language JSDoc comments */
|
|
@@ -1357,9 +1376,7 @@ declare function generateFormInterface(form: ParsedForm, entityLogicalName: stri
|
|
|
1357
1376
|
* @param options - Generator options
|
|
1358
1377
|
* @returns Array of { formName, interfaceName, content }
|
|
1359
1378
|
*/
|
|
1360
|
-
declare function generateEntityForms(forms: ParsedForm[], entityLogicalName: string, attributes: AttributeMetadata[], options?: FormGeneratorOptions): Array<{
|
|
1361
|
-
formName: string;
|
|
1362
|
-
interfaceName: string;
|
|
1379
|
+
declare function generateEntityForms(forms: ParsedForm[], entityLogicalName: string, attributes: AttributeMetadata[], options?: FormGeneratorOptions): Array<FormGenerationMeta & {
|
|
1363
1380
|
content: string;
|
|
1364
1381
|
}>;
|
|
1365
1382
|
|
|
@@ -1463,6 +1480,10 @@ declare function generateEntityNamesEnum(entityNames: string[], _options?: Entit
|
|
|
1463
1480
|
* export const NormalizePhone =
|
|
1464
1481
|
* createUnboundAction<NormalizePhoneParams, NormalizePhoneResult>('markant_NormalizePhone', { ... });
|
|
1465
1482
|
* ```
|
|
1483
|
+
*
|
|
1484
|
+
* Each executor assignment is emitted with a leading pure-call annotation (an `@__PURE__`
|
|
1485
|
+
* comment) so esbuild can tree-shake unused executors out of consumer bundles - importing a
|
|
1486
|
+
* single action from a large global module otherwise pulls in all of them (F-LMA9-01).
|
|
1466
1487
|
*/
|
|
1467
1488
|
|
|
1468
1489
|
/** Options for action/function generation */
|
|
@@ -1566,6 +1587,8 @@ interface EntityGenerationResult {
|
|
|
1566
1587
|
files: GeneratedFile[];
|
|
1567
1588
|
/** Warnings (e.g. missing labels, empty forms) */
|
|
1568
1589
|
warnings: string[];
|
|
1590
|
+
/** Per-form metadata for this entity (drives form-mapping.json, F-MAR7-04) */
|
|
1591
|
+
formMeta: FormGenerationMeta[];
|
|
1569
1592
|
}
|
|
1570
1593
|
/** A single generated file */
|
|
1571
1594
|
interface GeneratedFile {
|
|
@@ -1699,10 +1722,13 @@ declare class TypeGenerationOrchestrator {
|
|
|
1699
1722
|
*/
|
|
1700
1723
|
private getPicklistAttributes;
|
|
1701
1724
|
/**
|
|
1702
|
-
* Generate a form-mapping.json that maps entity
|
|
1703
|
-
*
|
|
1725
|
+
* Generate a form-mapping.json that maps each entity to its generated forms:
|
|
1726
|
+
* interface name, Fields/Tabs enum names, a main-form marker (isMain), and the
|
|
1727
|
+
* list of fields each form binds to. This lets AI agents pick the right form by
|
|
1728
|
+
* its fields without guessing interface names.
|
|
1704
1729
|
*
|
|
1705
|
-
*
|
|
1730
|
+
* Built from the structured per-form metadata collected during generation
|
|
1731
|
+
* (F-MAR7-04), not by parsing the generated code.
|
|
1706
1732
|
*/
|
|
1707
1733
|
private generateFormMapping;
|
|
1708
1734
|
}
|
package/dist/index.js
CHANGED
|
@@ -1954,6 +1954,7 @@ function singleQuoted(value) {
|
|
|
1954
1954
|
|
|
1955
1955
|
// src/generators/form-generator.ts
|
|
1956
1956
|
var FORM_TYPE_QUICK_CREATE2 = 7;
|
|
1957
|
+
var FORM_TYPE_MAIN2 = 2;
|
|
1957
1958
|
function specialControlToXrmType(controlType) {
|
|
1958
1959
|
switch (controlType) {
|
|
1959
1960
|
case "subgrid":
|
|
@@ -1996,14 +1997,7 @@ function labelToPascalMember(label) {
|
|
|
1996
1997
|
if (/^\d/.test(pascal)) return `_${pascal}`;
|
|
1997
1998
|
return pascal;
|
|
1998
1999
|
}
|
|
1999
|
-
function
|
|
2000
|
-
const labelConfig = options.labelConfig || DEFAULT_LABEL_CONFIG;
|
|
2001
|
-
const entityPascal = toPascalCase(entityLogicalName);
|
|
2002
|
-
const baseName = baseNameOverride || buildFormBaseName(entityPascal, toSafeFormName(form.name));
|
|
2003
|
-
const interfaceName = `${baseName}Form`;
|
|
2004
|
-
const fieldsTypeName = `${baseName}FormFields`;
|
|
2005
|
-
const attrMapName = `${baseName}FormAttributeMap`;
|
|
2006
|
-
const ctrlMapName = `${baseName}FormControlMap`;
|
|
2000
|
+
function collectFormFieldNames(form, attributeMap) {
|
|
2007
2001
|
const fieldNames = /* @__PURE__ */ new Set();
|
|
2008
2002
|
for (const control of form.allControls) {
|
|
2009
2003
|
if (control.datafieldname) {
|
|
@@ -2015,9 +2009,20 @@ function generateFormInterface(form, entityLogicalName, attributeMap, options =
|
|
|
2015
2009
|
fieldNames.add(systemField);
|
|
2016
2010
|
}
|
|
2017
2011
|
}
|
|
2012
|
+
return [...fieldNames].sort();
|
|
2013
|
+
}
|
|
2014
|
+
function generateFormInterface(form, entityLogicalName, attributeMap, options = {}, baseNameOverride) {
|
|
2015
|
+
const labelConfig = options.labelConfig || DEFAULT_LABEL_CONFIG;
|
|
2016
|
+
const entityPascal = toPascalCase(entityLogicalName);
|
|
2017
|
+
const baseName = baseNameOverride || buildFormBaseName(entityPascal, toSafeFormName(form.name));
|
|
2018
|
+
const interfaceName = `${baseName}Form`;
|
|
2019
|
+
const fieldsTypeName = `${baseName}FormFields`;
|
|
2020
|
+
const attrMapName = `${baseName}FormAttributeMap`;
|
|
2021
|
+
const ctrlMapName = `${baseName}FormControlMap`;
|
|
2022
|
+
const sortedFieldNames = collectFormFieldNames(form, attributeMap);
|
|
2018
2023
|
const fields = [];
|
|
2019
2024
|
const usedEnumNames = /* @__PURE__ */ new Set();
|
|
2020
|
-
for (const fieldName of
|
|
2025
|
+
for (const fieldName of sortedFieldNames) {
|
|
2021
2026
|
const attr = attributeMap.get(fieldName);
|
|
2022
2027
|
if (!attr) continue;
|
|
2023
2028
|
const primaryLabel = getPrimaryLabel(attr.DisplayName, labelConfig);
|
|
@@ -2211,7 +2216,7 @@ function generateFormInterface(form, entityLogicalName, attributeMap, options =
|
|
|
2211
2216
|
const sectionNames = tab.sections.filter((s) => s.name).map((s) => s.name);
|
|
2212
2217
|
if (sectionNames.length > 0) {
|
|
2213
2218
|
lines.push(` get(name: "${tab.name}"): Xrm.Controls.Tab & {`);
|
|
2214
|
-
lines.push(" sections: {");
|
|
2219
|
+
lines.push(" sections: Xrm.Collection.ItemCollection<Xrm.Controls.Section> & {");
|
|
2215
2220
|
for (const sectionName of sectionNames) {
|
|
2216
2221
|
lines.push(` get(name: "${sectionName}"): Xrm.Controls.Section;`);
|
|
2217
2222
|
}
|
|
@@ -2277,7 +2282,16 @@ function generateEntityForms(forms, entityLogicalName, attributes, options = {})
|
|
|
2277
2282
|
}
|
|
2278
2283
|
const interfaceName = `${baseName}Form`;
|
|
2279
2284
|
const content = generateFormInterface(form, entityLogicalName, attributeMap, options, baseName);
|
|
2280
|
-
|
|
2285
|
+
const hasNamedTabs = form.tabs.some((t) => t.name);
|
|
2286
|
+
results.push({
|
|
2287
|
+
formName: form.name,
|
|
2288
|
+
interfaceName,
|
|
2289
|
+
fieldsEnumName: `${baseName}FormFieldsEnum`,
|
|
2290
|
+
tabsEnumName: hasNamedTabs ? `${baseName}FormTabs` : "",
|
|
2291
|
+
fields: collectFormFieldNames(form, attributeMap),
|
|
2292
|
+
isMain: form.type === FORM_TYPE_MAIN2,
|
|
2293
|
+
content
|
|
2294
|
+
});
|
|
2281
2295
|
}
|
|
2282
2296
|
return results;
|
|
2283
2297
|
}
|
|
@@ -2518,27 +2532,27 @@ function generateActionModule(apis, isFunction, options = {}) {
|
|
|
2518
2532
|
if (apiInfo.api.bindingtype === 0 /* Global */) {
|
|
2519
2533
|
if (isFunction) {
|
|
2520
2534
|
const funcTypeArg = hasResult ? `<${name}Result>` : "<unknown>";
|
|
2521
|
-
lines.push(`export const ${name} = createUnboundFunction${funcTypeArg}('${apiInfo.api.uniquename}');`);
|
|
2535
|
+
lines.push(`export const ${name} = /* @__PURE__ */ createUnboundFunction${funcTypeArg}('${apiInfo.api.uniquename}');`);
|
|
2522
2536
|
} else if (hasParams) {
|
|
2523
2537
|
const paramMeta = generateParameterMetaMap(apiInfo.requestParameters);
|
|
2524
|
-
lines.push(`export const ${name} = createUnboundAction${typeArgs}('${apiInfo.api.uniquename}', ${paramMeta});`);
|
|
2538
|
+
lines.push(`export const ${name} = /* @__PURE__ */ createUnboundAction${typeArgs}('${apiInfo.api.uniquename}', ${paramMeta});`);
|
|
2525
2539
|
} else if (hasResult) {
|
|
2526
|
-
lines.push(`export const ${name} = createUnboundAction<${name}Result>('${apiInfo.api.uniquename}');`);
|
|
2540
|
+
lines.push(`export const ${name} = /* @__PURE__ */ createUnboundAction<${name}Result>('${apiInfo.api.uniquename}');`);
|
|
2527
2541
|
} else {
|
|
2528
|
-
lines.push(`export const ${name} = createUnboundAction('${apiInfo.api.uniquename}');`);
|
|
2542
|
+
lines.push(`export const ${name} = /* @__PURE__ */ createUnboundAction('${apiInfo.api.uniquename}');`);
|
|
2529
2543
|
}
|
|
2530
2544
|
} else {
|
|
2531
2545
|
const entity = apiInfo.api.boundentitylogicalname;
|
|
2532
2546
|
if (isFunction) {
|
|
2533
2547
|
const funcTypeArg = hasResult ? `<${name}Result>` : "<unknown>";
|
|
2534
|
-
lines.push(`export const ${name} = createBoundFunction${funcTypeArg}('${apiInfo.api.uniquename}', '${entity}');`);
|
|
2548
|
+
lines.push(`export const ${name} = /* @__PURE__ */ createBoundFunction${funcTypeArg}('${apiInfo.api.uniquename}', '${entity}');`);
|
|
2535
2549
|
} else if (hasParams) {
|
|
2536
2550
|
const paramMeta = generateParameterMetaMap(apiInfo.requestParameters);
|
|
2537
|
-
lines.push(`export const ${name} = createBoundAction${typeArgs}('${apiInfo.api.uniquename}', '${entity}', ${paramMeta});`);
|
|
2551
|
+
lines.push(`export const ${name} = /* @__PURE__ */ createBoundAction${typeArgs}('${apiInfo.api.uniquename}', '${entity}', ${paramMeta});`);
|
|
2538
2552
|
} else if (hasResult) {
|
|
2539
|
-
lines.push(`export const ${name} = createBoundAction<${name}Result>('${apiInfo.api.uniquename}', '${entity}');`);
|
|
2553
|
+
lines.push(`export const ${name} = /* @__PURE__ */ createBoundAction<${name}Result>('${apiInfo.api.uniquename}', '${entity}');`);
|
|
2540
2554
|
} else {
|
|
2541
|
-
lines.push(`export const ${name} = createBoundAction('${apiInfo.api.uniquename}', '${entity}');`);
|
|
2555
|
+
lines.push(`export const ${name} = /* @__PURE__ */ createBoundAction('${apiInfo.api.uniquename}', '${entity}');`);
|
|
2542
2556
|
}
|
|
2543
2557
|
}
|
|
2544
2558
|
lines.push("");
|
|
@@ -2753,9 +2767,9 @@ function generateBarrelIndex(files) {
|
|
|
2753
2767
|
lines.push("");
|
|
2754
2768
|
}
|
|
2755
2769
|
if (actions.length > 0) {
|
|
2756
|
-
lines.push("// Custom API Actions & Functions");
|
|
2770
|
+
lines.push("// Custom API Actions & Functions - import directly from individual files to avoid name conflicts:");
|
|
2757
2771
|
for (const f of actions) {
|
|
2758
|
-
lines.push(
|
|
2772
|
+
lines.push(`// import { ... } from '${toImportSpecifier(f.relativePath)}';`);
|
|
2759
2773
|
}
|
|
2760
2774
|
lines.push("");
|
|
2761
2775
|
}
|
|
@@ -2898,7 +2912,8 @@ var TypeGenerationOrchestrator = class {
|
|
|
2898
2912
|
entityResults.push({
|
|
2899
2913
|
entityLogicalName: entityName,
|
|
2900
2914
|
files: [],
|
|
2901
|
-
warnings: [`Failed to process: ${failedEntities.get(entityName)}`]
|
|
2915
|
+
warnings: [`Failed to process: ${failedEntities.get(entityName)}`],
|
|
2916
|
+
formMeta: []
|
|
2902
2917
|
});
|
|
2903
2918
|
continue;
|
|
2904
2919
|
}
|
|
@@ -2921,9 +2936,9 @@ var TypeGenerationOrchestrator = class {
|
|
|
2921
2936
|
type: "entity"
|
|
2922
2937
|
});
|
|
2923
2938
|
}
|
|
2924
|
-
const
|
|
2925
|
-
if (
|
|
2926
|
-
const formMapping = this.generateFormMapping(
|
|
2939
|
+
const entityFormMeta = entityResults.filter((r) => r.formMeta.length > 0).map((r) => ({ entityName: r.entityLogicalName, forms: r.formMeta }));
|
|
2940
|
+
if (entityFormMeta.length > 0) {
|
|
2941
|
+
const formMapping = this.generateFormMapping(entityFormMeta);
|
|
2927
2942
|
allFiles.push({
|
|
2928
2943
|
relativePath: "form-mapping.json",
|
|
2929
2944
|
content: JSON.stringify(formMapping, null, 2) + "\n",
|
|
@@ -3098,6 +3113,7 @@ var TypeGenerationOrchestrator = class {
|
|
|
3098
3113
|
generateEntityFiles(entityName, entityInfo) {
|
|
3099
3114
|
const warnings = [];
|
|
3100
3115
|
const files = [];
|
|
3116
|
+
const formMeta = [];
|
|
3101
3117
|
if (this.config.generateEntities) {
|
|
3102
3118
|
const entityContent = generateEntityInterface(entityInfo, {
|
|
3103
3119
|
labelConfig: this.config.labelConfig
|
|
@@ -3143,6 +3159,9 @@ var TypeGenerationOrchestrator = class {
|
|
|
3143
3159
|
content: addGeneratedHeader(combinedContent),
|
|
3144
3160
|
type: "form"
|
|
3145
3161
|
});
|
|
3162
|
+
for (const { content: _content, ...meta } of formResults) {
|
|
3163
|
+
formMeta.push(meta);
|
|
3164
|
+
}
|
|
3146
3165
|
}
|
|
3147
3166
|
} else {
|
|
3148
3167
|
warnings.push(`No forms found for ${entityName}`);
|
|
@@ -3163,7 +3182,7 @@ ${navPropsContent}` : fieldsEnumContent;
|
|
|
3163
3182
|
type: "fields"
|
|
3164
3183
|
});
|
|
3165
3184
|
}
|
|
3166
|
-
return { entityLogicalName: entityName, files, warnings };
|
|
3185
|
+
return { entityLogicalName: entityName, files, warnings, formMeta };
|
|
3167
3186
|
}
|
|
3168
3187
|
/**
|
|
3169
3188
|
* Generate Custom API Action/Function executor files.
|
|
@@ -3233,35 +3252,26 @@ ${navPropsContent}` : fieldsEnumContent;
|
|
|
3233
3252
|
return result;
|
|
3234
3253
|
}
|
|
3235
3254
|
/**
|
|
3236
|
-
* Generate a form-mapping.json that maps entity
|
|
3237
|
-
*
|
|
3255
|
+
* Generate a form-mapping.json that maps each entity to its generated forms:
|
|
3256
|
+
* interface name, Fields/Tabs enum names, a main-form marker (isMain), and the
|
|
3257
|
+
* list of fields each form binds to. This lets AI agents pick the right form by
|
|
3258
|
+
* its fields without guessing interface names.
|
|
3238
3259
|
*
|
|
3239
|
-
*
|
|
3260
|
+
* Built from the structured per-form metadata collected during generation
|
|
3261
|
+
* (F-MAR7-04), not by parsing the generated code.
|
|
3240
3262
|
*/
|
|
3241
|
-
generateFormMapping(
|
|
3263
|
+
generateFormMapping(entityForms) {
|
|
3242
3264
|
const mapping = {};
|
|
3243
|
-
for (const
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
const jsdocMatch = file.content.match(new RegExp(`/\\*\\*\\s*(.+?)\\s*\\*/\\s*export\\s+interface\\s+${interfaceName}`));
|
|
3254
|
-
const formName = jsdocMatch?.[1] ?? interfaceName.replace(/Form$/, "");
|
|
3255
|
-
forms.push({
|
|
3256
|
-
formName,
|
|
3257
|
-
interface: interfaceName,
|
|
3258
|
-
fieldsEnum: fieldsEnums[i] ?? "",
|
|
3259
|
-
tabsEnum: tabsEnums[i] ?? ""
|
|
3260
|
-
});
|
|
3261
|
-
}
|
|
3262
|
-
if (forms.length > 0) {
|
|
3263
|
-
mapping[entityName] = forms;
|
|
3264
|
-
}
|
|
3265
|
+
for (const { entityName, forms } of entityForms) {
|
|
3266
|
+
if (forms.length === 0) continue;
|
|
3267
|
+
mapping[entityName] = forms.map((f) => ({
|
|
3268
|
+
formName: f.formName,
|
|
3269
|
+
interface: f.interfaceName,
|
|
3270
|
+
fieldsEnum: f.fieldsEnumName,
|
|
3271
|
+
tabsEnum: f.tabsEnumName,
|
|
3272
|
+
isMain: f.isMain,
|
|
3273
|
+
fields: f.fields
|
|
3274
|
+
}));
|
|
3265
3275
|
}
|
|
3266
3276
|
return mapping;
|
|
3267
3277
|
}
|