@xrmforge/typegen 0.1.0 → 0.3.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.
package/dist/index.js CHANGED
@@ -1155,6 +1155,73 @@ var MetadataClient = class {
1155
1155
  log4.info(`Fetching type info for ${logicalNames.length} entities`);
1156
1156
  return Promise.all(logicalNames.map((name) => this.getEntityTypeInfo(name)));
1157
1157
  }
1158
+ // ─── Custom API Metadata ───────────────────────────────────────────────
1159
+ /**
1160
+ * Fetch all Custom APIs with their request parameters and response properties.
1161
+ *
1162
+ * Queries the customapi, customapirequestparameter, and customapiresponseproperty
1163
+ * tables and joins them into CustomApiTypeInfo objects.
1164
+ *
1165
+ * @param solutionFilter - Optional: filter by solution unique name
1166
+ * @returns Array of complete Custom API definitions
1167
+ */
1168
+ async getCustomApis(solutionFilter) {
1169
+ log4.info("Fetching Custom APIs...");
1170
+ let apiUrl = "/customapis?$select=uniquename,bindingtype,isfunction,boundentitylogicalname,displayname,description";
1171
+ if (solutionFilter) {
1172
+ const safeSolution = DataverseHttpClient.sanitizeIdentifier(solutionFilter);
1173
+ apiUrl += `&$filter=solutionid/uniquename eq '${safeSolution}'`;
1174
+ }
1175
+ const apis = await this.http.get(apiUrl);
1176
+ log4.info(`Found ${apis.value.length} Custom APIs`);
1177
+ if (apis.value.length === 0) return [];
1178
+ const params = await this.http.get(
1179
+ "/customapirequestparameters?$select=uniquename,type,isoptional,logicalentityname,description,_customapiid_value"
1180
+ );
1181
+ const props = await this.http.get(
1182
+ "/customapiresponseproperties?$select=uniquename,type,logicalentityname,description,_customapiid_value"
1183
+ );
1184
+ const paramsByApi = /* @__PURE__ */ new Map();
1185
+ for (const p of params.value) {
1186
+ const apiId = p._customapiid_value;
1187
+ if (!paramsByApi.has(apiId)) paramsByApi.set(apiId, []);
1188
+ paramsByApi.get(apiId).push({
1189
+ uniquename: p.uniquename,
1190
+ type: p.type,
1191
+ isoptional: p.isoptional,
1192
+ logicalentityname: p.logicalentityname,
1193
+ description: p.description
1194
+ });
1195
+ }
1196
+ const propsByApi = /* @__PURE__ */ new Map();
1197
+ for (const p of props.value) {
1198
+ const apiId = p._customapiid_value;
1199
+ if (!propsByApi.has(apiId)) propsByApi.set(apiId, []);
1200
+ propsByApi.get(apiId).push({
1201
+ uniquename: p.uniquename,
1202
+ type: p.type,
1203
+ logicalentityname: p.logicalentityname,
1204
+ description: p.description
1205
+ });
1206
+ }
1207
+ const result = [];
1208
+ for (const api of apis.value) {
1209
+ result.push({
1210
+ api: {
1211
+ uniquename: api.uniquename,
1212
+ bindingtype: api.bindingtype,
1213
+ isfunction: api.isfunction,
1214
+ boundentitylogicalname: api.boundentitylogicalname,
1215
+ displayname: api.displayname,
1216
+ description: api.description
1217
+ },
1218
+ requestParameters: paramsByApi.get(api.customapiid) ?? [],
1219
+ responseProperties: propsByApi.get(api.customapiid) ?? []
1220
+ });
1221
+ }
1222
+ log4.info(`Loaded ${result.length} Custom APIs with parameters and response properties`);
1223
+ return result;
1224
+ }
1158
1225
  // ─── Internal Helpers ──────────────────────────────────────────────────
1159
1226
  async getRelationships(logicalName) {
1160
1227
  const [oneToMany, manyToMany] = await Promise.all([
@@ -1487,6 +1554,37 @@ var FORM_CONTROL_TYPE_MAP = {
1487
1554
  Owner: "Xrm.Controls.LookupControl",
1488
1555
  PartyList: "Xrm.Controls.LookupControl"
1489
1556
  };
1557
+ function getFormMockValueType(attributeType) {
1558
+ const mapping = FORM_MOCK_VALUE_TYPE_MAP[attributeType];
1559
+ if (mapping) return mapping;
1560
+ return "unknown";
1561
+ }
1562
+ var FORM_MOCK_VALUE_TYPE_MAP = {
1563
+ // String types
1564
+ String: "string | null",
1565
+ Memo: "string | null",
1566
+ EntityName: "string | null",
1567
+ // Numeric types
1568
+ Integer: "number | null",
1569
+ BigInt: "number | null",
1570
+ Decimal: "number | null",
1571
+ Double: "number | null",
1572
+ Money: "number | null",
1573
+ // Boolean
1574
+ Boolean: "boolean | null",
1575
+ // OptionSet types (numeric values at runtime)
1576
+ Picklist: "number | null",
1577
+ State: "number | null",
1578
+ Status: "number | null",
1579
+ MultiSelectPicklist: "number[] | null",
1580
+ // Date/Time
1581
+ DateTime: "Date | null",
1582
+ // Lookup types
1583
+ Lookup: "Xrm.LookupValue[] | null",
1584
+ Customer: "Xrm.LookupValue[] | null",
1585
+ Owner: "Xrm.LookupValue[] | null",
1586
+ PartyList: "Xrm.LookupValue[] | null"
1587
+ };
1490
1588
  function toSafeIdentifier(logicalName) {
1491
1589
  if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(logicalName)) {
1492
1590
  return logicalName;
@@ -1531,6 +1629,42 @@ function shouldIncludeInEntityInterface(attr) {
1531
1629
  return true;
1532
1630
  }
1533
1631
 
1632
+ // src/generators/activity-party.ts
1633
+ function generateActivityPartyInterface(namespace = "XrmForge.Entities") {
1634
+ const lines = [];
1635
+ lines.push(`declare namespace ${namespace} {`);
1636
+ lines.push("");
1637
+ lines.push(" /**");
1638
+ lines.push(" * Activity Party - Teilnehmer einer Aktivit\xE4t (E-Mail, Termin, etc.)");
1639
+ lines.push(" * Wird von PartyList-Feldern (to, from, cc, bcc, requiredattendees) referenziert.");
1640
+ lines.push(" * @see https://learn.microsoft.com/en-us/power-apps/developer/data-platform/reference/entities/activityparty");
1641
+ lines.push(" */");
1642
+ lines.push(" interface ActivityParty {");
1643
+ lines.push(" /** Primary key */");
1644
+ lines.push(" activitypartyid?: string;");
1645
+ lines.push(" /** Referenz auf die zugeh\xF6rige Aktivit\xE4t */");
1646
+ lines.push(" _activityid_value?: string;");
1647
+ lines.push(" /** Referenz auf den Teilnehmer (account | contact | systemuser | queue | knowledgearticle) */");
1648
+ lines.push(" _partyid_value?: string;");
1649
+ lines.push(" /**");
1650
+ lines.push(" * Rolle des Teilnehmers:");
1651
+ lines.push(" * 1=Sender, 2=To, 3=CC, 4=BCC, 5=Required Attendee,");
1652
+ lines.push(" * 6=Optional Attendee, 7=Organizer, 8=Regarding, 9=Owner,");
1653
+ lines.push(" * 10=Resource, 11=Customer, 12=Chat Participant, 13=Related");
1654
+ lines.push(" */");
1655
+ lines.push(" participationtypemask?: number;");
1656
+ lines.push(" /** E-Mail-Adresse f\xFCr die Zustellung */");
1657
+ lines.push(" addressused?: string;");
1658
+ lines.push(" /** Aufwand des Teilnehmers (bei Serviceterminen) */");
1659
+ lines.push(" effort?: number;");
1660
+ lines.push(" /** Name des Teilnehmers (wenn nicht aufgel\xF6st) */");
1661
+ lines.push(" unresolvedpartyname?: string;");
1662
+ lines.push(" }");
1663
+ lines.push("}");
1664
+ lines.push("");
1665
+ return lines.join("\n");
1666
+ }
1667
+
1534
1668
  // src/generators/label-utils.ts
1535
1669
  function getSecondaryLabel(label, config) {
1536
1670
  if (!config.secondaryLanguage) return void 0;
@@ -1901,6 +2035,15 @@ function generateFormInterface(form, entityLogicalName, attributeMap, options =
1901
2035
  lines.push(" } & Xrm.Ui;");
1902
2036
  }
1903
2037
  lines.push(" }");
2038
+ const mockValuesName = `${interfaceName}MockValues`;
2039
+ lines.push("");
2040
+ lines.push(` /** Mock value types for "${form.name}" form (used with @xrmforge/testing) */`);
2041
+ lines.push(` type ${mockValuesName} = {`);
2042
+ for (const field of fields) {
2043
+ const mockType = getFormMockValueType(field.attributeType);
2044
+ lines.push(` ${field.logicalName}?: ${mockType};`);
2045
+ }
2046
+ lines.push(" };");
1904
2047
  lines.push("}");
1905
2048
  lines.push("");
1906
2049
  return lines.join("\n");
@@ -1939,6 +2082,587 @@ function generateEntityForms(forms, entityLogicalName, attributes, options = {})
1939
2082
  return results;
1940
2083
  }
1941
2084
 
2085
+ // src/generators/entity-fields-generator.ts
2086
+ function labelToPascalMember2(label) {
2087
+ if (!label) return "";
2088
+ const transliterated = transliterateUmlauts(label);
2089
+ const cleaned = transliterated.replace(/[^a-zA-Z0-9\s_]/g, "");
2090
+ const parts = cleaned.split(/[\s_]+/).filter((p) => p.length > 0);
2091
+ if (parts.length === 0) return "";
2092
+ const pascal = parts.map((p) => p.charAt(0).toUpperCase() + p.slice(1).toLowerCase()).join("");
2093
+ if (/^\d/.test(pascal)) return `_${pascal}`;
2094
+ return pascal;
2095
+ }
2096
+ function generateEntityFieldsEnum(info, options = {}) {
2097
+ const labelConfig = options.labelConfig || DEFAULT_LABEL_CONFIG;
2098
+ const namespace = options.namespace || "XrmForge.Entities";
2099
+ const entityName = toPascalCase(info.entity.LogicalName);
2100
+ const enumName = `${entityName}Fields`;
2101
+ const includedAttrs = info.attributes.filter(shouldIncludeInEntityInterface).sort((a, b) => a.LogicalName.localeCompare(b.LogicalName));
2102
+ const lines = [];
2103
+ lines.push(`declare namespace ${namespace} {`);
2104
+ lines.push("");
2105
+ lines.push(` /** All fields of ${entityName} (for Web API $select queries) */`);
2106
+ lines.push(` const enum ${enumName} {`);
2107
+ const usedNames = /* @__PURE__ */ new Set();
2108
+ for (const attr of includedAttrs) {
2109
+ const isLookup = isLookupType(attr.AttributeType);
2110
+ const propertyName = isLookup ? toLookupValueProperty(attr.LogicalName) : attr.LogicalName;
2111
+ const primaryLabel = getPrimaryLabel(attr.DisplayName, labelConfig);
2112
+ let memberName = labelToPascalMember2(primaryLabel);
2113
+ if (!memberName) {
2114
+ memberName = toPascalCase(attr.LogicalName);
2115
+ }
2116
+ const originalName = memberName;
2117
+ let counter = 2;
2118
+ while (usedNames.has(memberName)) {
2119
+ memberName = `${originalName}${counter}`;
2120
+ counter++;
2121
+ }
2122
+ usedNames.add(memberName);
2123
+ const dualLabel = getJSDocLabel(attr.DisplayName, labelConfig);
2124
+ if (dualLabel) {
2125
+ lines.push(` /** ${dualLabel} */`);
2126
+ }
2127
+ lines.push(` ${memberName} = '${propertyName}',`);
2128
+ }
2129
+ lines.push(" }");
2130
+ lines.push("}");
2131
+ lines.push("");
2132
+ return lines.join("\n");
2133
+ }
2134
+ function generateEntityNavigationProperties(info, options = {}) {
2135
+ const labelConfig = options.labelConfig || DEFAULT_LABEL_CONFIG;
2136
+ const namespace = options.namespace || "XrmForge.Entities";
2137
+ const entityName = toPascalCase(info.entity.LogicalName);
2138
+ const enumName = `${entityName}NavigationProperties`;
2139
+ const lookupAttrs = info.attributes.filter(shouldIncludeInEntityInterface).filter((a) => isLookupType(a.AttributeType)).sort((a, b) => a.LogicalName.localeCompare(b.LogicalName));
2140
+ if (lookupAttrs.length === 0) return "";
2141
+ const lines = [];
2142
+ lines.push(`declare namespace ${namespace} {`);
2143
+ lines.push("");
2144
+ lines.push(` /** Navigation properties of ${entityName} (for parseLookup and $expand) */`);
2145
+ lines.push(` const enum ${enumName} {`);
2146
+ const usedNames = /* @__PURE__ */ new Set();
2147
+ for (const attr of lookupAttrs) {
2148
+ const primaryLabel = getPrimaryLabel(attr.DisplayName, labelConfig);
2149
+ let memberName = labelToPascalMember2(primaryLabel);
2150
+ if (!memberName) {
2151
+ memberName = toPascalCase(attr.LogicalName);
2152
+ }
2153
+ const originalName = memberName;
2154
+ let counter = 2;
2155
+ while (usedNames.has(memberName)) {
2156
+ memberName = `${originalName}${counter}`;
2157
+ counter++;
2158
+ }
2159
+ usedNames.add(memberName);
2160
+ const dualLabel = getJSDocLabel(attr.DisplayName, labelConfig);
2161
+ if (dualLabel) {
2162
+ lines.push(` /** ${dualLabel} */`);
2163
+ }
2164
+ lines.push(` ${memberName} = '${attr.LogicalName}',`);
2165
+ }
2166
+ lines.push(" }");
2167
+ lines.push("}");
2168
+ lines.push("");
2169
+ return lines.join("\n");
2170
+ }
2171
+
2172
+ // src/generators/entity-names-generator.ts
2173
+ function generateEntityNamesEnum(entityNames, options = {}) {
2174
+ const namespace = options.namespace ?? "XrmForge";
2175
+ const sorted = [...entityNames].sort();
2176
+ const lines = [];
2177
+ lines.push(`declare namespace ${namespace} {`);
2178
+ lines.push(" /** Entity logical names for Xrm.WebApi calls (compile-time only, zero runtime) */");
2179
+ lines.push(" const enum EntityNames {");
2180
+ for (const name of sorted) {
2181
+ const pascal = toPascalCase(name);
2182
+ lines.push(` ${pascal} = '${name}',`);
2183
+ }
2184
+ lines.push(" }");
2185
+ lines.push("}");
2186
+ lines.push("");
2187
+ return lines.join("\n");
2188
+ }
2189
+
2190
+ // src/generators/webapi-helpers.ts
2191
+ function select(...fields) {
2192
+ if (fields.length === 0) return "";
2193
+ return `?$select=${fields.join(",")}`;
2194
+ }
2195
+ function parseLookup(response, navigationProperty) {
2196
+ const key = `_${navigationProperty}_value`;
2197
+ const id = response[key];
2198
+ if (!id) return null;
2199
+ return {
2200
+ id,
2201
+ name: response[`${key}@OData.Community.Display.V1.FormattedValue`] ?? "",
2202
+ entityType: response[`${key}@Microsoft.Dynamics.CRM.lookuplogicalname`] ?? ""
2203
+ };
2204
+ }
2205
+ function parseLookups(response, navigationProperties) {
2206
+ const result = {};
2207
+ for (const prop of navigationProperties) {
2208
+ result[prop] = parseLookup(response, prop);
2209
+ }
2210
+ return result;
2211
+ }
2212
+ function parseFormattedValue(response, fieldName) {
2213
+ return response[`${fieldName}@OData.Community.Display.V1.FormattedValue`] ?? null;
2214
+ }
2215
+ function selectExpand(fields, expand) {
2216
+ const parts = [];
2217
+ if (fields.length > 0) parts.push(`$select=${fields.join(",")}`);
2218
+ if (expand) parts.push(`$expand=${expand}`);
2219
+ return parts.length > 0 ? `?${parts.join("&")}` : "";
2220
+ }
2221
+
2222
+ // src/generators/xrm-constants.ts
2223
+ var DisplayState = /* @__PURE__ */ ((DisplayState2) => {
2224
+ DisplayState2["Expanded"] = "expanded";
2225
+ DisplayState2["Collapsed"] = "collapsed";
2226
+ return DisplayState2;
2227
+ })(DisplayState || {});
2228
+ var FormNotificationLevel = /* @__PURE__ */ ((FormNotificationLevel2) => {
2229
+ FormNotificationLevel2["Error"] = "ERROR";
2230
+ FormNotificationLevel2["Warning"] = "WARNING";
2231
+ FormNotificationLevel2["Info"] = "INFO";
2232
+ return FormNotificationLevel2;
2233
+ })(FormNotificationLevel || {});
2234
+ var RequiredLevel = /* @__PURE__ */ ((RequiredLevel2) => {
2235
+ RequiredLevel2["None"] = "none";
2236
+ RequiredLevel2["Required"] = "required";
2237
+ RequiredLevel2["Recommended"] = "recommended";
2238
+ return RequiredLevel2;
2239
+ })(RequiredLevel || {});
2240
+ var SubmitMode = /* @__PURE__ */ ((SubmitMode2) => {
2241
+ SubmitMode2["Always"] = "always";
2242
+ SubmitMode2["Never"] = "never";
2243
+ SubmitMode2["Dirty"] = "dirty";
2244
+ return SubmitMode2;
2245
+ })(SubmitMode || {});
2246
+ var SaveMode = /* @__PURE__ */ ((SaveMode2) => {
2247
+ SaveMode2[SaveMode2["Save"] = 1] = "Save";
2248
+ SaveMode2[SaveMode2["SaveAndClose"] = 2] = "SaveAndClose";
2249
+ SaveMode2[SaveMode2["Deactivate"] = 5] = "Deactivate";
2250
+ SaveMode2[SaveMode2["Reactivate"] = 6] = "Reactivate";
2251
+ SaveMode2[SaveMode2["Send"] = 7] = "Send";
2252
+ SaveMode2[SaveMode2["Disqualify"] = 15] = "Disqualify";
2253
+ SaveMode2[SaveMode2["Qualify"] = 16] = "Qualify";
2254
+ SaveMode2[SaveMode2["Assign"] = 47] = "Assign";
2255
+ SaveMode2[SaveMode2["SaveAsCompleted"] = 58] = "SaveAsCompleted";
2256
+ SaveMode2[SaveMode2["SaveAndNew"] = 59] = "SaveAndNew";
2257
+ SaveMode2[SaveMode2["AutoSave"] = 70] = "AutoSave";
2258
+ return SaveMode2;
2259
+ })(SaveMode || {});
2260
+ var ClientType = /* @__PURE__ */ ((ClientType2) => {
2261
+ ClientType2["Web"] = "Web";
2262
+ ClientType2["Outlook"] = "Outlook";
2263
+ ClientType2["Mobile"] = "Mobile";
2264
+ return ClientType2;
2265
+ })(ClientType || {});
2266
+ var ClientState = /* @__PURE__ */ ((ClientState2) => {
2267
+ ClientState2["Online"] = "Online";
2268
+ ClientState2["Offline"] = "Offline";
2269
+ return ClientState2;
2270
+ })(ClientState || {});
2271
+ var OperationType = /* @__PURE__ */ ((OperationType2) => {
2272
+ OperationType2[OperationType2["Action"] = 0] = "Action";
2273
+ OperationType2[OperationType2["Function"] = 1] = "Function";
2274
+ OperationType2[OperationType2["CRUD"] = 2] = "CRUD";
2275
+ return OperationType2;
2276
+ })(OperationType || {});
2277
+ var StructuralProperty = /* @__PURE__ */ ((StructuralProperty2) => {
2278
+ StructuralProperty2[StructuralProperty2["Unknown"] = 0] = "Unknown";
2279
+ StructuralProperty2[StructuralProperty2["PrimitiveType"] = 1] = "PrimitiveType";
2280
+ StructuralProperty2[StructuralProperty2["ComplexType"] = 2] = "ComplexType";
2281
+ StructuralProperty2[StructuralProperty2["EnumerationType"] = 3] = "EnumerationType";
2282
+ StructuralProperty2[StructuralProperty2["Collection"] = 4] = "Collection";
2283
+ StructuralProperty2[StructuralProperty2["EntityType"] = 5] = "EntityType";
2284
+ return StructuralProperty2;
2285
+ })(StructuralProperty || {});
2286
+ var BindingType = /* @__PURE__ */ ((BindingType2) => {
2287
+ BindingType2[BindingType2["Global"] = 0] = "Global";
2288
+ BindingType2[BindingType2["Entity"] = 1] = "Entity";
2289
+ BindingType2[BindingType2["EntityCollection"] = 2] = "EntityCollection";
2290
+ return BindingType2;
2291
+ })(BindingType || {});
2292
+
2293
+ // src/generators/action-runtime.ts
2294
+ function executeRequest(request) {
2295
+ return Xrm.WebApi.online.execute(request);
2296
+ }
2297
+ function executeMultiple(requests) {
2298
+ return Xrm.WebApi.online.executeMultiple(requests);
2299
+ }
2300
+ function cleanRecordId(id) {
2301
+ return id.replace(/[{}]/g, "");
2302
+ }
2303
+ function buildBoundRequest(operationName, entityLogicalName, operationType, recordId, paramMeta, params) {
2304
+ const parameterTypes = {
2305
+ entity: {
2306
+ typeName: `mscrm.${entityLogicalName}`,
2307
+ structuralProperty: 5 /* EntityType */
2308
+ }
2309
+ };
2310
+ if (paramMeta) {
2311
+ for (const [key, meta] of Object.entries(paramMeta)) {
2312
+ parameterTypes[key] = meta;
2313
+ }
2314
+ }
2315
+ const request = {
2316
+ getMetadata: () => ({
2317
+ boundParameter: "entity",
2318
+ parameterTypes,
2319
+ operationName,
2320
+ operationType
2321
+ }),
2322
+ entity: {
2323
+ id: cleanRecordId(recordId),
2324
+ entityType: entityLogicalName
2325
+ }
2326
+ };
2327
+ if (params) {
2328
+ for (const [key, value] of Object.entries(params)) {
2329
+ request[key] = value;
2330
+ }
2331
+ }
2332
+ return request;
2333
+ }
2334
+ function buildUnboundRequest(operationName, operationType, paramMeta, params) {
2335
+ const parameterTypes = {};
2336
+ if (paramMeta) {
2337
+ for (const [key, meta] of Object.entries(paramMeta)) {
2338
+ parameterTypes[key] = meta;
2339
+ }
2340
+ }
2341
+ const request = {
2342
+ getMetadata: () => ({
2343
+ boundParameter: null,
2344
+ parameterTypes,
2345
+ operationName,
2346
+ operationType
2347
+ })
2348
+ };
2349
+ if (params) {
2350
+ for (const [key, value] of Object.entries(params)) {
2351
+ request[key] = value;
2352
+ }
2353
+ }
2354
+ return request;
2355
+ }
2356
+ function createBoundAction(operationName, entityLogicalName, paramMeta) {
2357
+ return {
2358
+ execute(recordId, params) {
2359
+ const req = buildBoundRequest(
2360
+ operationName,
2361
+ entityLogicalName,
2362
+ 0 /* Action */,
2363
+ recordId,
2364
+ paramMeta,
2365
+ params
2366
+ );
2367
+ return executeRequest(req);
2368
+ },
2369
+ request(recordId, params) {
2370
+ return buildBoundRequest(
2371
+ operationName,
2372
+ entityLogicalName,
2373
+ 0 /* Action */,
2374
+ recordId,
2375
+ paramMeta,
2376
+ params
2377
+ );
2378
+ }
2379
+ };
2380
+ }
2381
+ function createUnboundAction(operationName, paramMeta) {
2382
+ return {
2383
+ async execute(params) {
2384
+ const req = buildUnboundRequest(
2385
+ operationName,
2386
+ 0 /* Action */,
2387
+ paramMeta,
2388
+ params
2389
+ );
2390
+ const response = await executeRequest(req);
2391
+ if (!response.ok) {
2392
+ const errorText = await response.text();
2393
+ throw new Error(errorText);
2394
+ }
2395
+ if (response.status !== 204) {
2396
+ return response.json();
2397
+ }
2398
+ return response;
2399
+ },
2400
+ request(params) {
2401
+ return buildUnboundRequest(
2402
+ operationName,
2403
+ 0 /* Action */,
2404
+ paramMeta,
2405
+ params
2406
+ );
2407
+ }
2408
+ };
2409
+ }
2410
+ function createUnboundFunction(operationName) {
2411
+ return {
2412
+ async execute() {
2413
+ const req = buildUnboundRequest(operationName, 1 /* Function */);
2414
+ const response = await executeRequest(req);
2415
+ if (!response.ok) {
2416
+ const errorText = await response.text();
2417
+ throw new Error(errorText);
2418
+ }
2419
+ return response.json();
2420
+ },
2421
+ request() {
2422
+ return buildUnboundRequest(operationName, 1 /* Function */);
2423
+ }
2424
+ };
2425
+ }
2426
+ function createBoundFunction(operationName, entityLogicalName) {
2427
+ return {
2428
+ async execute(recordId) {
2429
+ const req = buildBoundRequest(
2430
+ operationName,
2431
+ entityLogicalName,
2432
+ 1 /* Function */,
2433
+ recordId
2434
+ );
2435
+ const response = await executeRequest(req);
2436
+ if (!response.ok) {
2437
+ const errorText = await response.text();
2438
+ throw new Error(errorText);
2439
+ }
2440
+ return response.json();
2441
+ },
2442
+ request(recordId) {
2443
+ return buildBoundRequest(
2444
+ operationName,
2445
+ entityLogicalName,
2446
+ 1 /* Function */,
2447
+ recordId
2448
+ );
2449
+ }
2450
+ };
2451
+ }
2452
+ async function withProgress(message, operation) {
2453
+ Xrm.Utility.showProgressIndicator(message);
2454
+ try {
2455
+ return await operation();
2456
+ } catch (error) {
2457
+ const msg = error instanceof Error ? error.message : String(error);
2458
+ Xrm.Navigation.openErrorDialog({ message: msg });
2459
+ throw error;
2460
+ } finally {
2461
+ Xrm.Utility.closeProgressIndicator();
2462
+ }
2463
+ }
2464
+
2465
+ // src/metadata/custom-api-types.ts
2466
+ function mapCustomApiParameterType(type, entityName) {
2467
+ switch (type) {
2468
+ case 0 /* Boolean */:
2469
+ return { tsType: "boolean", typeName: "Edm.Boolean", structuralProperty: 1 };
2470
+ case 1 /* DateTime */:
2471
+ return { tsType: "string", typeName: "Edm.DateTimeOffset", structuralProperty: 1 };
2472
+ case 2 /* Decimal */:
2473
+ return { tsType: "number", typeName: "Edm.Decimal", structuralProperty: 1 };
2474
+ case 3 /* Entity */:
2475
+ return {
2476
+ tsType: "Record<string, unknown>",
2477
+ typeName: `mscrm.${entityName || "crmbaseentity"}`,
2478
+ structuralProperty: 5
2479
+ };
2480
+ case 4 /* EntityCollection */:
2481
+ return {
2482
+ tsType: "Array<Record<string, unknown>>",
2483
+ typeName: "Collection(mscrm.crmbaseentity)",
2484
+ structuralProperty: 4
2485
+ };
2486
+ case 5 /* EntityReference */:
2487
+ return {
2488
+ tsType: "{ id: string; entityType: string; name?: string }",
2489
+ typeName: `mscrm.${entityName || "crmbaseentity"}`,
2490
+ structuralProperty: 5
2491
+ };
2492
+ case 6 /* Float */:
2493
+ return { tsType: "number", typeName: "Edm.Double", structuralProperty: 1 };
2494
+ case 7 /* Integer */:
2495
+ return { tsType: "number", typeName: "Edm.Int32", structuralProperty: 1 };
2496
+ case 8 /* Money */:
2497
+ return { tsType: "number", typeName: "Edm.Decimal", structuralProperty: 1 };
2498
+ case 9 /* Picklist */:
2499
+ return { tsType: "number", typeName: "Edm.Int32", structuralProperty: 1 };
2500
+ case 10 /* String */:
2501
+ return { tsType: "string", typeName: "Edm.String", structuralProperty: 1 };
2502
+ case 11 /* StringArray */:
2503
+ return { tsType: "string[]", typeName: "Collection(Edm.String)", structuralProperty: 4 };
2504
+ case 12 /* Guid */:
2505
+ return { tsType: "string", typeName: "Edm.Guid", structuralProperty: 1 };
2506
+ default:
2507
+ return { tsType: "unknown", typeName: "Edm.String", structuralProperty: 0 };
2508
+ }
2509
+ }
2510
+
2511
+ // src/generators/action-generator.ts
2512
+ function deriveActionName(uniquename) {
2513
+ const withoutPrefix = uniquename.includes("_") ? uniquename.substring(uniquename.indexOf("_") + 1) : uniquename;
2514
+ return toPascalCase(withoutPrefix);
2515
+ }
2516
+ function generateParamsInterface(name, params) {
2517
+ if (params.length === 0) return "";
2518
+ const lines = [];
2519
+ lines.push(` interface ${name}Params {`);
2520
+ for (const param of params) {
2521
+ const mapped = mapCustomApiParameterType(param.type, param.logicalentityname);
2522
+ const optional = param.isoptional ? "?" : "";
2523
+ if (param.description) {
2524
+ lines.push(` /** ${param.description} */`);
2525
+ }
2526
+ lines.push(` ${param.uniquename}${optional}: ${mapped.tsType};`);
2527
+ }
2528
+ lines.push(" }");
2529
+ return lines.join("\n");
2530
+ }
2531
+ function generateResultInterface(name, props) {
2532
+ if (props.length === 0) return "";
2533
+ const lines = [];
2534
+ lines.push(` interface ${name}Result {`);
2535
+ for (const prop of props) {
2536
+ const mapped = mapCustomApiParameterType(prop.type, prop.logicalentityname);
2537
+ if (prop.description) {
2538
+ lines.push(` /** ${prop.description} */`);
2539
+ }
2540
+ lines.push(` ${prop.uniquename}: ${mapped.tsType};`);
2541
+ }
2542
+ lines.push(" }");
2543
+ return lines.join("\n");
2544
+ }
2545
+ function generateActionDeclarations(apis, isFunction, entityName, options = {}) {
2546
+ const baseNamespace = isFunction ? options.functionsNamespace || "XrmForge.Functions" : options.actionsNamespace || "XrmForge.Actions";
2547
+ const namespace = entityName ? `${baseNamespace}.${toPascalCase(entityName)}` : baseNamespace;
2548
+ const lines = [];
2549
+ lines.push("// Auto-generated by @xrmforge/typegen - DO NOT EDIT");
2550
+ lines.push("");
2551
+ lines.push(`declare namespace ${namespace} {`);
2552
+ for (const apiInfo of apis) {
2553
+ const name = deriveActionName(apiInfo.api.uniquename);
2554
+ const hasParams = apiInfo.requestParameters.length > 0;
2555
+ const hasResult = apiInfo.responseProperties.length > 0;
2556
+ lines.push("");
2557
+ const description = apiInfo.api.description || apiInfo.api.displayname || apiInfo.api.uniquename;
2558
+ lines.push(` /** ${description} (${apiInfo.api.uniquename}) */`);
2559
+ if (hasParams) {
2560
+ lines.push(generateParamsInterface(name, apiInfo.requestParameters));
2561
+ lines.push("");
2562
+ }
2563
+ if (hasResult) {
2564
+ lines.push(generateResultInterface(name, apiInfo.responseProperties));
2565
+ lines.push("");
2566
+ }
2567
+ const importFrom = options.importPath || "@xrmforge/typegen";
2568
+ if (apiInfo.api.bindingtype === 0 /* Global */) {
2569
+ if (hasParams && hasResult) {
2570
+ lines.push(` const ${name}: import('${importFrom}').UnboundActionWithParamsExecutor<${name}Params, ${name}Result>;`);
2571
+ } else if (hasParams) {
2572
+ lines.push(` const ${name}: import('${importFrom}').UnboundActionWithParamsExecutor<${name}Params, void>;`);
2573
+ } else {
2574
+ lines.push(` const ${name}: import('${importFrom}').UnboundActionExecutor;`);
2575
+ }
2576
+ } else {
2577
+ if (hasParams) {
2578
+ lines.push(` const ${name}: import('${importFrom}').BoundActionWithParamsExecutor<${name}Params>;`);
2579
+ } else {
2580
+ lines.push(` const ${name}: import('${importFrom}').BoundActionExecutor;`);
2581
+ }
2582
+ }
2583
+ }
2584
+ lines.push("}");
2585
+ lines.push("");
2586
+ return lines.join("\n");
2587
+ }
2588
+ function generateActionModule(apis, isFunction, options = {}) {
2589
+ const importPath = options.importPath || "@xrmforge/typegen";
2590
+ const lines = [];
2591
+ lines.push("// Auto-generated by @xrmforge/typegen - DO NOT EDIT");
2592
+ lines.push("");
2593
+ const imports = /* @__PURE__ */ new Set();
2594
+ for (const apiInfo of apis) {
2595
+ if (apiInfo.api.bindingtype === 0 /* Global */) {
2596
+ if (isFunction) {
2597
+ imports.add("createUnboundFunction");
2598
+ } else {
2599
+ imports.add("createUnboundAction");
2600
+ }
2601
+ } else {
2602
+ if (isFunction) {
2603
+ imports.add("createBoundFunction");
2604
+ } else {
2605
+ imports.add("createBoundAction");
2606
+ }
2607
+ }
2608
+ }
2609
+ lines.push(`import { ${[...imports].sort().join(", ")} } from '${importPath}';`);
2610
+ lines.push("");
2611
+ for (const apiInfo of apis) {
2612
+ const name = deriveActionName(apiInfo.api.uniquename);
2613
+ const hasParams = apiInfo.requestParameters.length > 0;
2614
+ const description = apiInfo.api.description || apiInfo.api.displayname || apiInfo.api.uniquename;
2615
+ lines.push(`/** ${description} */`);
2616
+ if (apiInfo.api.bindingtype === 0 /* Global */) {
2617
+ if (isFunction) {
2618
+ lines.push(`export const ${name} = createUnboundFunction('${apiInfo.api.uniquename}');`);
2619
+ } else if (hasParams) {
2620
+ const paramMeta = generateParameterMetaMap(apiInfo.requestParameters);
2621
+ lines.push(`export const ${name} = createUnboundAction('${apiInfo.api.uniquename}', ${paramMeta});`);
2622
+ } else {
2623
+ lines.push(`export const ${name} = createUnboundAction('${apiInfo.api.uniquename}');`);
2624
+ }
2625
+ } else {
2626
+ const entity = apiInfo.api.boundentitylogicalname;
2627
+ if (isFunction) {
2628
+ lines.push(`export const ${name} = createBoundFunction('${apiInfo.api.uniquename}', '${entity}');`);
2629
+ } else if (hasParams) {
2630
+ const paramMeta = generateParameterMetaMap(apiInfo.requestParameters);
2631
+ lines.push(`export const ${name} = createBoundAction('${apiInfo.api.uniquename}', '${entity}', ${paramMeta});`);
2632
+ } else {
2633
+ lines.push(`export const ${name} = createBoundAction('${apiInfo.api.uniquename}', '${entity}');`);
2634
+ }
2635
+ }
2636
+ lines.push("");
2637
+ }
2638
+ return lines.join("\n");
2639
+ }
2640
+ function generateParameterMetaMap(params) {
2641
+ const entries = [];
2642
+ for (const param of params) {
2643
+ const mapped = mapCustomApiParameterType(param.type, param.logicalentityname);
2644
+ entries.push(
2645
+ ` ${param.uniquename}: { typeName: '${mapped.typeName}', structuralProperty: ${mapped.structuralProperty} }`
2646
+ );
2647
+ }
2648
+ return `{
2649
+ ${entries.join(",\n")},
2650
+ }`;
2651
+ }
2652
+ function groupCustomApis(apis) {
2653
+ const actions = /* @__PURE__ */ new Map();
2654
+ const functions = /* @__PURE__ */ new Map();
2655
+ for (const api of apis) {
2656
+ const target = api.api.isfunction ? functions : actions;
2657
+ const key = api.api.bindingtype === 0 /* Global */ ? "global" : api.api.boundentitylogicalname || "global";
2658
+ if (!target.has(key)) {
2659
+ target.set(key, []);
2660
+ }
2661
+ target.get(key).push(api);
2662
+ }
2663
+ return { actions, functions };
2664
+ }
2665
+
1942
2666
  // src/orchestrator/file-writer.ts
1943
2667
  import { mkdir, writeFile, readFile } from "fs/promises";
1944
2668
  import { join as join2, dirname } from "path";
@@ -2032,6 +2756,8 @@ var TypeGenerationOrchestrator = class {
2032
2756
  generateEntities: config.generateEntities ?? true,
2033
2757
  generateForms: config.generateForms ?? true,
2034
2758
  generateOptionSets: config.generateOptionSets ?? true,
2759
+ generateActions: config.generateActions ?? false,
2760
+ actionsFilter: config.actionsFilter ?? "",
2035
2761
  useCache: config.useCache ?? false,
2036
2762
  cacheDir: config.cacheDir ?? ".xrmforge/cache",
2037
2763
  namespacePrefix: config.namespacePrefix ?? "XrmForge"
@@ -2106,6 +2832,63 @@ var TypeGenerationOrchestrator = class {
2106
2832
  });
2107
2833
  }
2108
2834
  }
2835
+ if (this.config.generateActions && !signal?.aborted) {
2836
+ this.logger.info("Fetching Custom APIs...");
2837
+ let customApis = await metadataClient.getCustomApis();
2838
+ if (this.config.actionsFilter) {
2839
+ const prefix = this.config.actionsFilter.toLowerCase();
2840
+ const before = customApis.length;
2841
+ customApis = customApis.filter((api) => api.api.uniquename.toLowerCase().startsWith(prefix));
2842
+ this.logger.info(`Filtered Custom APIs by prefix "${this.config.actionsFilter}": ${before} -> ${customApis.length}`);
2843
+ }
2844
+ if (customApis.length > 0) {
2845
+ const importPath = "@xrmforge/typegen";
2846
+ const grouped = groupCustomApis(customApis);
2847
+ for (const [key, apis] of grouped.actions) {
2848
+ const entityName = key === "global" ? void 0 : key;
2849
+ const declarations = generateActionDeclarations(apis, false, entityName, { importPath });
2850
+ const module = generateActionModule(apis, false, { importPath });
2851
+ allFiles.push({
2852
+ relativePath: `actions/${key}.d.ts`,
2853
+ content: addGeneratedHeader(declarations),
2854
+ type: "action"
2855
+ });
2856
+ allFiles.push({
2857
+ relativePath: `actions/${key}.ts`,
2858
+ content: addGeneratedHeader(module),
2859
+ type: "action"
2860
+ });
2861
+ }
2862
+ for (const [key, apis] of grouped.functions) {
2863
+ const entityName = key === "global" ? void 0 : key;
2864
+ const declarations = generateActionDeclarations(apis, true, entityName, { importPath });
2865
+ const module = generateActionModule(apis, true, { importPath });
2866
+ allFiles.push({
2867
+ relativePath: `functions/${key}.d.ts`,
2868
+ content: addGeneratedHeader(declarations),
2869
+ type: "action"
2870
+ });
2871
+ allFiles.push({
2872
+ relativePath: `functions/${key}.ts`,
2873
+ content: addGeneratedHeader(module),
2874
+ type: "action"
2875
+ });
2876
+ }
2877
+ this.logger.info(`Generated ${grouped.actions.size} action groups, ${grouped.functions.size} function groups`);
2878
+ } else {
2879
+ this.logger.info("No Custom APIs found");
2880
+ }
2881
+ }
2882
+ if (this.config.entities.length > 0) {
2883
+ const entityNamesContent = generateEntityNamesEnum(this.config.entities, {
2884
+ namespace: this.config.namespacePrefix
2885
+ });
2886
+ allFiles.push({
2887
+ relativePath: "entity-names.d.ts",
2888
+ content: addGeneratedHeader(entityNamesContent),
2889
+ type: "entity"
2890
+ });
2891
+ }
2109
2892
  if (allFiles.length > 0) {
2110
2893
  const indexContent = generateBarrelIndex(allFiles);
2111
2894
  const indexFile = {
@@ -2232,12 +3015,17 @@ var TypeGenerationOrchestrator = class {
2232
3015
  export {
2233
3016
  ApiRequestError,
2234
3017
  AuthenticationError,
3018
+ BindingType,
3019
+ ClientState,
3020
+ ClientType,
2235
3021
  ConfigError,
2236
3022
  ConsoleLogSink,
2237
3023
  DEFAULT_LABEL_CONFIG,
2238
3024
  DataverseHttpClient,
3025
+ DisplayState,
2239
3026
  ErrorCode,
2240
3027
  FastXmlParser,
3028
+ FormNotificationLevel,
2241
3029
  GenerationError,
2242
3030
  JsonLogSink,
2243
3031
  LogLevel,
@@ -2245,18 +3033,35 @@ export {
2245
3033
  MetadataCache,
2246
3034
  MetadataClient,
2247
3035
  MetadataError,
3036
+ OperationType,
3037
+ RequiredLevel,
3038
+ SaveMode,
2248
3039
  SilentLogSink,
3040
+ StructuralProperty,
3041
+ SubmitMode,
2249
3042
  TypeGenerationOrchestrator,
2250
3043
  XrmForgeError,
2251
3044
  configureLogging,
3045
+ createBoundAction,
3046
+ createBoundFunction,
2252
3047
  createCredential,
2253
3048
  createLogger,
3049
+ createUnboundAction,
3050
+ createUnboundFunction,
2254
3051
  defaultXmlParser,
2255
3052
  disambiguateEnumMembers,
3053
+ executeMultiple,
3054
+ executeRequest,
2256
3055
  extractControlFields,
2257
3056
  getJSDocLabel as formatDualLabel,
3057
+ generateActionDeclarations,
3058
+ generateActionModule,
3059
+ generateActivityPartyInterface,
3060
+ generateEntityFieldsEnum,
2258
3061
  generateEntityForms,
2259
3062
  generateEntityInterface,
3063
+ generateEntityNamesEnum,
3064
+ generateEntityNavigationProperties,
2260
3065
  generateEntityOptionSets,
2261
3066
  generateEnumMembers,
2262
3067
  generateFormInterface,
@@ -2264,18 +3069,27 @@ export {
2264
3069
  getEntityPropertyType,
2265
3070
  getFormAttributeType,
2266
3071
  getFormControlType,
3072
+ getFormMockValueType,
2267
3073
  getJSDocLabel,
2268
3074
  getLabelLanguagesParam,
2269
3075
  getPrimaryLabel,
2270
3076
  getSecondaryLabel,
3077
+ groupCustomApis,
2271
3078
  isLookupType,
3079
+ isPartyListType,
2272
3080
  isRateLimitError,
2273
3081
  isXrmForgeError,
2274
3082
  labelToIdentifier,
2275
3083
  parseForm,
3084
+ parseFormattedValue,
3085
+ parseLookup,
3086
+ parseLookups,
3087
+ select,
3088
+ selectExpand,
2276
3089
  shouldIncludeInEntityInterface,
2277
3090
  toLookupValueProperty,
2278
3091
  toPascalCase,
2279
- toSafeIdentifier
3092
+ toSafeIdentifier,
3093
+ withProgress
2280
3094
  };
2281
3095
  //# sourceMappingURL=index.js.map