@mohsinonxrm/dataverse-sdk-metadata 1.0.0 → 2.0.0-beta.1
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/README.md +366 -77
- package/dist/index.d.ts +2588 -11530
- package/dist/index.js +1160 -890
- package/dist/index.js.map +1 -1
- package/package.json +13 -9
package/README.md
CHANGED
|
@@ -20,7 +20,8 @@ Complete TypeScript metadata management for Microsoft Dataverse - create, read,
|
|
|
20
20
|
- ✅ **Relationship eligibility** - check which entities can participate in relationships
|
|
21
21
|
- ✅ **Metadata change tracking** - RetrieveMetadataChanges with version stamps
|
|
22
22
|
- ✅ **Strict typing** - separate Read/Create/Update types, internal fields excluded
|
|
23
|
-
- ✅ **
|
|
23
|
+
- ✅ **Builders** - `Metadata.entity`, `Metadata.string`, `Metadata.picklist`, `Metadata.lookup`, … return the create payloads with labels, `RequiredLevel` and `@odata.type` filled in
|
|
24
|
+
- ✅ **ESM-only** - works in Node 22+ and browsers
|
|
24
25
|
|
|
25
26
|
## Installation
|
|
26
27
|
|
|
@@ -51,11 +52,12 @@ const entityId = await metadataClient.createEntityDefinition({
|
|
|
51
52
|
DisplayCollectionName: {
|
|
52
53
|
LocalizedLabels: [{ Label: "Projects", LanguageCode: 1033 }],
|
|
53
54
|
},
|
|
54
|
-
OwnershipType:
|
|
55
|
+
OwnershipType: "UserOwned", // or OwnershipTypes.UserOwned
|
|
56
|
+
IsActivity: false,
|
|
55
57
|
HasActivities: true,
|
|
56
58
|
HasNotes: true,
|
|
57
59
|
Attributes: [
|
|
58
|
-
//
|
|
60
|
+
// The primary name column comes first (required)
|
|
59
61
|
{
|
|
60
62
|
"@odata.type": "Microsoft.Dynamics.CRM.StringAttributeMetadata",
|
|
61
63
|
SchemaName: "new_Name",
|
|
@@ -72,6 +74,111 @@ const entityId = await metadataClient.createEntityDefinition({
|
|
|
72
74
|
|
|
73
75
|
---
|
|
74
76
|
|
|
77
|
+
## Builders
|
|
78
|
+
|
|
79
|
+
The `Metadata` builders write the create payloads for you. Each is a plain function that returns
|
|
80
|
+
the create type `MetadataClient` takes (`EntityMetadataCreate`, `StringAttributeMetadataCreate`,
|
|
81
|
+
`OneToManyRelationshipMetadataCreate`, …), with the parts that are easy to get wrong filled in:
|
|
82
|
+
the `@odata.type`, labels with their `LanguageCode`, `RequiredLevel`, a local choice's
|
|
83
|
+
`IsGlobal: false`, a global choice's bind path, and Dataverse's defaults.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import { Metadata, MetadataClient } from "@mohsinonxrm/dataverse-sdk-metadata";
|
|
87
|
+
|
|
88
|
+
// A table: its primary name column first, then any other columns
|
|
89
|
+
await metadataClient.createEntityDefinition(
|
|
90
|
+
Metadata.entity("new_Project", {
|
|
91
|
+
displayName: "Project",
|
|
92
|
+
displayCollectionName: "Projects",
|
|
93
|
+
description: "Projects we run",
|
|
94
|
+
primaryName: Metadata.primaryName("new_Name", { displayName: "Name" }),
|
|
95
|
+
attributes: [
|
|
96
|
+
Metadata.string("new_Code", { displayName: "Code", maxLength: 20 }),
|
|
97
|
+
Metadata.dateTime("new_DueDate", { displayName: "Due date", format: "DateOnly" }),
|
|
98
|
+
],
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// Columns
|
|
103
|
+
await metadataClient.createAttributeDefinition(
|
|
104
|
+
"new_project",
|
|
105
|
+
Metadata.picklist("new_Status", {
|
|
106
|
+
displayName: "Status",
|
|
107
|
+
requiredLevel: "ApplicationRequired",
|
|
108
|
+
options: ["Not started", "In progress", { label: "Done", value: 100000009 }],
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
await metadataClient.createAttributeDefinition(
|
|
112
|
+
"new_project",
|
|
113
|
+
Metadata.money("new_Budget", { displayName: "Budget", minValue: 0 })
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
// A lookup is created with its 1:N relationship
|
|
117
|
+
await metadataClient.createOneToManyRelationship(
|
|
118
|
+
Metadata.lookup("new_AccountId", {
|
|
119
|
+
displayName: "Account",
|
|
120
|
+
referencingEntity: "new_project", // gets the lookup column
|
|
121
|
+
referencedEntity: "account", // the table it points to
|
|
122
|
+
relationshipSchemaName: "new_account_project",
|
|
123
|
+
})
|
|
124
|
+
);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
| Builder | Returns | Defaults |
|
|
128
|
+
| ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
|
129
|
+
| `Metadata.label(text, lcid?)`, `Metadata.label({ 1033: "Name", 1036: "Nom" })` | `Label` | language 1033 |
|
|
130
|
+
| `Metadata.entity(schemaName, …)` | `EntityMetadataCreate` | `UserOwned`, no activities, no notes |
|
|
131
|
+
| `Metadata.primaryName(schemaName, …)` | `PrimaryNameAttributeMetadataCreate` | `MaxLength` 100 |
|
|
132
|
+
| `Metadata.string(schemaName, …)` | `StringAttributeMetadataCreate` | `MaxLength` 100, format `Text` |
|
|
133
|
+
| `Metadata.memo(schemaName, …)` | `MemoAttributeMetadataCreate` | `MaxLength` 2,000 |
|
|
134
|
+
| `Metadata.integer(schemaName, …)` | `IntegerAttributeMetadataCreate` | format `None` |
|
|
135
|
+
| `Metadata.decimal` / `Metadata.double(schemaName, …)` | `DecimalAttributeMetadataCreate` / `DoubleAttributeMetadataCreate` | precision 2 |
|
|
136
|
+
| `Metadata.money(schemaName, …)` | `MoneyAttributeMetadataCreate` | the currency's precision |
|
|
137
|
+
| `Metadata.dateTime(schemaName, …)` | `DateTimeAttributeMetadataCreate` | `DateAndTime` |
|
|
138
|
+
| `Metadata.boolean(schemaName, …)` | `BooleanAttributeMetadataCreate` | Yes (1) / No (0), default No |
|
|
139
|
+
| `Metadata.picklist` / `Metadata.multiSelectPicklist` | `PicklistAttributeMetadataCreate` / `MultiSelectPicklistAttributeMetadataCreate` | `options` or `globalOptionSetId`, exactly one |
|
|
140
|
+
| `Metadata.file` / `Metadata.image(schemaName, …)` | `FileAttributeMetadataCreate` / `ImageAttributeMetadataCreate` | 32 MB / 10 MB |
|
|
141
|
+
| `Metadata.lookup(schemaName, …)` | `OneToManyRelationshipMetadataCreate` | Dataverse's cascade and the referenced table's primary key |
|
|
142
|
+
| `Metadata.manyToMany(schemaName, …)` | `ManyToManyRelationshipMetadataCreate` | intersect table = the schema name, lowercased |
|
|
143
|
+
| `Metadata.globalOptionSet(name, …)` | `OptionSetMetadataCreate` | `IsGlobal: true`, `Picklist` |
|
|
144
|
+
|
|
145
|
+
Every column takes `displayName` (required), `description`, `requiredLevel` (`"None"`, the
|
|
146
|
+
default, `"Recommended"` or `"ApplicationRequired"`) and `languageCode`. Labels given as text
|
|
147
|
+
are in English (1033) unless you pass `languageCode`; Dataverse needs a label in the
|
|
148
|
+
organization's base language. Pass a `Label` (from `Metadata.label`) for several languages.
|
|
149
|
+
|
|
150
|
+
The types reject what Dataverse would: a choice with both `options` and a `globalOptionSetId` (or
|
|
151
|
+
neither), the `"DateOnly"` behavior without the `"DateOnly"` format, `SystemRequired`, a table
|
|
152
|
+
without its primary name or with a lookup among its `attributes`. At run time a builder throws
|
|
153
|
+
`DataverseInvalidRequestError` (`InvalidParameter`) for a schema name without the customization
|
|
154
|
+
prefix, an empty label, a length, precision or range out of bounds, a minimum above the maximum,
|
|
155
|
+
duplicate option values or a `globalOptionSetId` that isn't a GUID.
|
|
156
|
+
|
|
157
|
+
Choice options are labels (`"Small"`), which Dataverse numbers from the publisher's choice value
|
|
158
|
+
prefix, or `{ label, value, description, color }`. A global choice is bound by its `MetadataId`:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
const id = await metadataClient.createGlobalOptionSet(
|
|
162
|
+
Metadata.globalOptionSet("new_priority", { displayName: "Priority", options: ["High", "Low"] })
|
|
163
|
+
);
|
|
164
|
+
await metadataClient.createAttributeDefinition(
|
|
165
|
+
"new_project",
|
|
166
|
+
Metadata.picklist("new_Priority", { displayName: "Priority", globalOptionSetId: id })
|
|
167
|
+
);
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
A builder's result is an ordinary object of the create type: spread it to set a property the
|
|
171
|
+
builder doesn't take, and the compiler still checks it.
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
await metadataClient.createAttributeDefinition("new_project", {
|
|
175
|
+
...Metadata.string("new_Code", { displayName: "Code" }),
|
|
176
|
+
IsAuditEnabled: { Value: true },
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
75
182
|
## Entity Operations
|
|
76
183
|
|
|
77
184
|
### Create Entity
|
|
@@ -83,7 +190,7 @@ const metadataId = await metadataClient.createEntityDefinition({
|
|
|
83
190
|
DisplayName: { LocalizedLabels: [{ Label: "Task", LanguageCode: 1033 }] },
|
|
84
191
|
DisplayCollectionName: { LocalizedLabels: [{ Label: "Tasks", LanguageCode: 1033 }] },
|
|
85
192
|
Description: { LocalizedLabels: [{ Label: "Project task tracking", LanguageCode: 1033 }] },
|
|
86
|
-
OwnershipType:
|
|
193
|
+
OwnershipType: "UserOwned", // or OwnershipTypes.UserOwned
|
|
87
194
|
IsActivity: false,
|
|
88
195
|
HasActivities: true,
|
|
89
196
|
HasNotes: true,
|
|
@@ -113,19 +220,106 @@ const customEntities = await metadataClient.getEntityDefinitions({
|
|
|
113
220
|
const projectEntity = await metadataClient.getEntityDefinition("new_project", {
|
|
114
221
|
expand: "Attributes",
|
|
115
222
|
});
|
|
223
|
+
|
|
224
|
+
// Labels in specific languages (LCIDs), sent as LabelLanguages
|
|
225
|
+
const labels = await metadataClient.getEntityDefinition("account", {
|
|
226
|
+
select: ["DisplayName"],
|
|
227
|
+
labelLanguages: [1033, 1036],
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Entity Set Names
|
|
232
|
+
|
|
233
|
+
`getEntitySetName()` reads the entity set name used in Web API URLs from metadata, so you don't
|
|
234
|
+
have to guess the plural. The first call loads every table's name in one request and caches the
|
|
235
|
+
map on the `MetadataClient`; `clearEntitySetNameCache()` forgets it.
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
const set = await metadataClient.getEntitySetName("opportunity"); // "opportunities"
|
|
239
|
+
const record = await client.api(`${set}(${id})`).get();
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Read Columns by Type
|
|
243
|
+
|
|
244
|
+
Type-specific properties, such as a choice column's options, a lookup's `Targets` or a text
|
|
245
|
+
column's `MaxLength`, are only available through a type cast. Pass `cast` and the results are
|
|
246
|
+
typed accordingly:
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
// GET EntityDefinitions(LogicalName='account')/Attributes/Microsoft.Dynamics.CRM.PicklistAttributeMetadata
|
|
250
|
+
// ?LabelLanguages=1033&$select=LogicalName&$expand=OptionSet($select=Options),GlobalOptionSet($select=Options)
|
|
251
|
+
const picklists = await metadataClient.getAttributeDefinitions("account", {
|
|
252
|
+
cast: "PicklistAttributeMetadata",
|
|
253
|
+
select: ["LogicalName"],
|
|
254
|
+
expand: ["OptionSet($select=Options)", "GlobalOptionSet($select=Options)"],
|
|
255
|
+
labelLanguages: 1033,
|
|
256
|
+
});
|
|
257
|
+
const options = picklists[0]?.OptionSet?.Options; // picklists is PicklistAttributeMetadata[]
|
|
258
|
+
|
|
259
|
+
const lookups = await metadataClient.getAttributeDefinitions("account", {
|
|
260
|
+
cast: "LookupAttributeMetadata",
|
|
261
|
+
select: ["LogicalName", "Targets"],
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const name = await metadataClient.getAttributeDefinition("account", "name", {
|
|
265
|
+
cast: "StringAttributeMetadata",
|
|
266
|
+
select: ["MaxLength", "Format"],
|
|
267
|
+
});
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
`cast` takes any attribute metadata type name: `StringAttributeMetadata`, `MemoAttributeMetadata`,
|
|
271
|
+
`IntegerAttributeMetadata`, `BigIntAttributeMetadata`, `DecimalAttributeMetadata`,
|
|
272
|
+
`DoubleAttributeMetadata`, `MoneyAttributeMetadata`, `DateTimeAttributeMetadata`,
|
|
273
|
+
`BooleanAttributeMetadata`, `PicklistAttributeMetadata`, `MultiSelectPicklistAttributeMetadata`,
|
|
274
|
+
`StateAttributeMetadata`, `StatusAttributeMetadata`, `LookupAttributeMetadata`,
|
|
275
|
+
`FileAttributeMetadata`, `ImageAttributeMetadata`, `UniqueIdentifierAttributeMetadata`,
|
|
276
|
+
`EntityNameAttributeMetadata`, `EnumAttributeMetadata` or `ManagedPropertyAttributeMetadata`.
|
|
277
|
+
|
|
278
|
+
Key values (logical names, schema names, option set names) are escaped, so a name containing `'`
|
|
279
|
+
is sent as `'O''Brien'`.
|
|
280
|
+
|
|
281
|
+
Every `create*` method returns the new component's `MetadataId`. Dataverse answers a metadata
|
|
282
|
+
create with `204 No Content` and the id in the `OData-EntityId` header, which is where the SDK
|
|
283
|
+
reads it.
|
|
284
|
+
|
|
285
|
+
### Create in a Solution
|
|
286
|
+
|
|
287
|
+
Every create and update takes an optional last argument, `{ solutionUniqueName }`, sent as the
|
|
288
|
+
`MSCRM.SolutionUniqueName` header. The component is added to that unmanaged solution; without it
|
|
289
|
+
the change goes to the default solution.
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
const metadataId = await metadataClient.createEntityDefinition(definition, {
|
|
293
|
+
solutionUniqueName: "ContosoProjects",
|
|
294
|
+
});
|
|
116
295
|
```
|
|
117
296
|
|
|
118
297
|
### Update Entity
|
|
119
298
|
|
|
299
|
+
Metadata updates use `PUT`, which **replaces** the whole definition: a property you leave out is
|
|
300
|
+
reset to its default. Either send the complete definition, with its `@odata.type`, to
|
|
301
|
+
`updateEntityDefinition()`, or let the SDK retrieve it, apply your changes and send it back:
|
|
302
|
+
|
|
120
303
|
```typescript
|
|
121
|
-
//
|
|
304
|
+
// Change only the description; every other property keeps its current value
|
|
305
|
+
await metadataClient.mergeAndUpdateEntityDefinition("new_project", {
|
|
306
|
+
Description: { LocalizedLabels: [{ Label: "Manage projects and tasks", LanguageCode: 1033 }] },
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Or retrieve, change and send the whole definition yourself
|
|
310
|
+
const current = await metadataClient.getEntityDefinition("new_project");
|
|
122
311
|
await metadataClient.updateEntityDefinition("new_project", {
|
|
312
|
+
...current,
|
|
123
313
|
"@odata.type": "Microsoft.Dynamics.CRM.EntityMetadata",
|
|
124
|
-
|
|
125
|
-
Description: { LocalizedLabels: [{ Label: "Manage projects and tasks", LanguageCode: 1033 }] },
|
|
314
|
+
HasNotes: true,
|
|
126
315
|
});
|
|
127
316
|
```
|
|
128
317
|
|
|
318
|
+
Columns, relationships and global choices have the same pair: `updateAttributeDefinition()` /
|
|
319
|
+
`mergeAndUpdateAttributeDefinition()`, `updateOneToManyRelationship()` and
|
|
320
|
+
`updateManyToManyRelationship()` / `mergeAndUpdateRelationship()`, and `updateGlobalOptionSet()` /
|
|
321
|
+
`mergeAndUpdateGlobalOptionSet()`. See [Update Operations Use PUT](#update-operations-use-put).
|
|
322
|
+
|
|
129
323
|
### Delete Entity
|
|
130
324
|
|
|
131
325
|
```typescript
|
|
@@ -270,6 +464,7 @@ const relationshipId = await metadataClient.createOneToManyRelationship({
|
|
|
270
464
|
ReferencedAttribute: "new_projectid",
|
|
271
465
|
ReferencingEntity: "new_task",
|
|
272
466
|
Lookup: {
|
|
467
|
+
"@odata.type": "Microsoft.Dynamics.CRM.LookupAttributeMetadata",
|
|
273
468
|
SchemaName: "new_ProjectId",
|
|
274
469
|
DisplayName: { LocalizedLabels: [{ Label: "Project", LanguageCode: 1033 }] },
|
|
275
470
|
RequiredLevel: { Value: "ApplicationRequired" },
|
|
@@ -317,6 +512,7 @@ const relationshipId = await metadataClient.createOneToManyRelationship({
|
|
|
317
512
|
ReferencedAttribute: "accountid",
|
|
318
513
|
ReferencingEntity: "new_project", // "Many" side (child)
|
|
319
514
|
Lookup: {
|
|
515
|
+
"@odata.type": "Microsoft.Dynamics.CRM.LookupAttributeMetadata",
|
|
320
516
|
SchemaName: "new_AccountId",
|
|
321
517
|
DisplayName: { LocalizedLabels: [{ Label: "Account", LanguageCode: 1033 }] },
|
|
322
518
|
RequiredLevel: { Value: "None" },
|
|
@@ -374,30 +570,60 @@ const optionSetId = await metadataClient.createGlobalOptionSet({
|
|
|
374
570
|
|
|
375
571
|
### Use Global Choice in Column
|
|
376
572
|
|
|
573
|
+
A choice or choices (multi-select) column uses an existing global choice through
|
|
574
|
+
`"GlobalOptionSet@odata.bind"` instead of an `OptionSet` of its own; the create types accept
|
|
575
|
+
exactly one of the two. Dataverse binds by `MetadataId` only (`(Name='…')` is rejected), so keep
|
|
576
|
+
the id `createGlobalOptionSet` returns, or read it with `getGlobalOptionSet(name)`.
|
|
577
|
+
|
|
377
578
|
```typescript
|
|
378
|
-
//
|
|
579
|
+
// Bind the column to the global choice created above, by its MetadataId
|
|
379
580
|
await metadataClient.createAttributeDefinition("new_project", {
|
|
380
581
|
"@odata.type": "Microsoft.Dynamics.CRM.PicklistAttributeMetadata",
|
|
381
582
|
SchemaName: "new_Type",
|
|
382
583
|
DisplayName: { LocalizedLabels: [{ Label: "Type", LanguageCode: 1033 }] },
|
|
383
584
|
RequiredLevel: { Value: "None" },
|
|
384
|
-
GlobalOptionSet: {
|
|
385
|
-
|
|
386
|
-
|
|
585
|
+
"GlobalOptionSet@odata.bind": `/GlobalOptionSetDefinitions(${optionSetId})`,
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
// A choices (multi-select) column on a global choice found by name
|
|
589
|
+
const { MetadataId: projectTypeId } = await metadataClient.getGlobalOptionSet("new_projecttype", {
|
|
590
|
+
select: ["MetadataId"],
|
|
591
|
+
});
|
|
592
|
+
await metadataClient.createAttributeDefinition("new_project", {
|
|
593
|
+
"@odata.type": "Microsoft.Dynamics.CRM.MultiSelectPicklistAttributeMetadata",
|
|
594
|
+
SchemaName: "new_Types",
|
|
595
|
+
DisplayName: { LocalizedLabels: [{ Label: "Types", LanguageCode: 1033 }] },
|
|
596
|
+
RequiredLevel: { Value: "None" },
|
|
597
|
+
"GlobalOptionSet@odata.bind": `/GlobalOptionSetDefinitions(${projectTypeId})`,
|
|
387
598
|
});
|
|
388
599
|
```
|
|
389
600
|
|
|
390
601
|
### Insert Option Value
|
|
391
602
|
|
|
603
|
+
The option-value methods (`insertOptionValue`, `updateOptionValue`, `deleteOptionValue`,
|
|
604
|
+
`orderOptions`) take either `OptionSetName`, for a global choice, or `EntityLogicalName` and
|
|
605
|
+
`AttributeLogicalName`, for a column's own choice; passing both, or neither, is a type error and
|
|
606
|
+
throws `DataverseInvalidRequestError` before anything is sent. A column bound to a global choice
|
|
607
|
+
has no options of its own, so change the global choice. `insertStatusValue` always takes the
|
|
608
|
+
table and its `statuscode` column. `updateOptionValue` requires `MergeLabels`.
|
|
609
|
+
|
|
392
610
|
```typescript
|
|
393
|
-
// Add new option to
|
|
611
|
+
// Add a new option to a column's own (local) choice
|
|
394
612
|
const result = await metadataClient.insertOptionValue({
|
|
613
|
+
EntityLogicalName: "new_project",
|
|
614
|
+
AttributeLogicalName: "new_status",
|
|
615
|
+
Label: { LocalizedLabels: [{ Label: "On Hold", LanguageCode: 1033 }] },
|
|
616
|
+
Value: 4, // Optional, auto-assigned if omitted
|
|
617
|
+
});
|
|
618
|
+
console.log("New option value:", result.NewOptionValue);
|
|
619
|
+
|
|
620
|
+
// Add a new option to a global choice
|
|
621
|
+
const added = await metadataClient.insertOptionValue({
|
|
395
622
|
OptionSetName: "new_projecttype",
|
|
396
623
|
Label: { LocalizedLabels: [{ Label: "Maintenance", LanguageCode: 1033 }] },
|
|
397
|
-
Value: 100003,
|
|
624
|
+
Value: 100003,
|
|
398
625
|
});
|
|
399
|
-
|
|
400
|
-
console.log("New option value:", result.NewOptionValue);
|
|
626
|
+
console.log("New option value:", added.NewOptionValue);
|
|
401
627
|
```
|
|
402
628
|
|
|
403
629
|
---
|
|
@@ -408,9 +634,9 @@ console.log("New option value:", result.NewOptionValue);
|
|
|
408
634
|
|
|
409
635
|
```typescript
|
|
410
636
|
// Create customer lookup (targets account OR contact)
|
|
411
|
-
const result = await metadataClient.
|
|
637
|
+
const result = await metadataClient.createCustomerRelationship({
|
|
412
638
|
Lookup: {
|
|
413
|
-
"@odata.type": "Microsoft.Dynamics.CRM.
|
|
639
|
+
"@odata.type": "Microsoft.Dynamics.CRM.ComplexLookupAttributeMetadata",
|
|
414
640
|
SchemaName: "new_CustomerId",
|
|
415
641
|
DisplayName: { LocalizedLabels: [{ Label: "Customer", LanguageCode: 1033 }] },
|
|
416
642
|
RequiredLevel: { Value: "ApplicationRequired" },
|
|
@@ -438,7 +664,7 @@ const result = await metadataClient.createCustomerRelationships({
|
|
|
438
664
|
|
|
439
665
|
```typescript
|
|
440
666
|
// Create lookup targeting multiple custom entities
|
|
441
|
-
const result = await metadataClient.
|
|
667
|
+
const result = await metadataClient.createPolymorphicLookup({
|
|
442
668
|
OneToManyRelationships: [
|
|
443
669
|
{
|
|
444
670
|
"@odata.type": "Microsoft.Dynamics.CRM.OneToManyRelationshipMetadata",
|
|
@@ -456,6 +682,7 @@ const result = await metadataClient.createPolymorphicLookupAttribute({
|
|
|
456
682
|
},
|
|
457
683
|
],
|
|
458
684
|
Lookup: {
|
|
685
|
+
"@odata.type": "Microsoft.Dynamics.CRM.ComplexLookupAttributeMetadata",
|
|
459
686
|
SchemaName: "new_RegardingId",
|
|
460
687
|
DisplayName: { LocalizedLabels: [{ Label: "Regarding", LanguageCode: 1033 }] },
|
|
461
688
|
RequiredLevel: { Value: "None" },
|
|
@@ -467,89 +694,129 @@ const result = await metadataClient.createPolymorphicLookupAttribute({
|
|
|
467
694
|
|
|
468
695
|
## Relationship Eligibility Checks
|
|
469
696
|
|
|
697
|
+
`CanBeReferenced`, `CanBeReferencing` and `CanManyToMany` are Web API actions (sent as `POST`);
|
|
698
|
+
the `GetValid*` methods are functions (sent as `GET`). Each method returns the plain value.
|
|
699
|
+
|
|
470
700
|
```typescript
|
|
471
|
-
//
|
|
472
|
-
const
|
|
473
|
-
console.log("Can be parent:", canBeReferenced.CanBeReferenced);
|
|
701
|
+
// Can the table be the parent (referenced) table in a 1:N relationship?
|
|
702
|
+
const canBeParent: boolean = await metadataClient.canBeReferenced("new_project");
|
|
474
703
|
|
|
475
|
-
//
|
|
476
|
-
const
|
|
477
|
-
console.log("Can be child:", canBeReferencing.CanBeReferencing);
|
|
704
|
+
// Can the table be the child (referencing) table in a 1:N relationship?
|
|
705
|
+
const canBeChild: boolean = await metadataClient.canBeReferencing("new_task");
|
|
478
706
|
|
|
479
|
-
//
|
|
480
|
-
const
|
|
481
|
-
console.log("Can do N:N:", canManyToMany.CanManyToMany);
|
|
707
|
+
// Can the table take part in an N:N relationship?
|
|
708
|
+
const canDoManyToMany: boolean = await metadataClient.canManyToMany("new_project");
|
|
482
709
|
|
|
483
|
-
//
|
|
484
|
-
const
|
|
485
|
-
|
|
710
|
+
// Tables that can reference account, and tables that contact can reference
|
|
711
|
+
const children: string[] = await metadataClient.getValidReferencingEntities("account");
|
|
712
|
+
const parents: string[] = await metadataClient.getValidReferencedEntities("contact");
|
|
713
|
+
|
|
714
|
+
// Every table that can take part in an N:N relationship (no argument)
|
|
715
|
+
const manyToManyTables: string[] = await metadataClient.getValidManyToMany();
|
|
486
716
|
```
|
|
487
717
|
|
|
488
718
|
---
|
|
489
719
|
|
|
490
720
|
## RetrieveMetadataChanges
|
|
491
721
|
|
|
492
|
-
Track metadata changes
|
|
722
|
+
Track metadata changes with version stamps for incremental sync. The method is positional:
|
|
723
|
+
`retrieveMetadataChanges(query, clientVersionStamp?, deletedMetadataFilters?)`.
|
|
724
|
+
`RetrieveMetadataChanges` is a Web API function, so the SDK sends it as a `GET` with the query as
|
|
725
|
+
a JSON parameter alias.
|
|
493
726
|
|
|
494
727
|
```typescript
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
728
|
+
import {
|
|
729
|
+
LogicalOperator,
|
|
730
|
+
MetadataConditionOperator,
|
|
731
|
+
type EntityQueryExpression,
|
|
732
|
+
} from "@mohsinonxrm/dataverse-sdk-metadata";
|
|
733
|
+
|
|
734
|
+
const query: EntityQueryExpression = {
|
|
735
|
+
Criteria: {
|
|
736
|
+
FilterOperator: LogicalOperator.And,
|
|
737
|
+
Conditions: [
|
|
738
|
+
{
|
|
739
|
+
PropertyName: "IsCustomEntity",
|
|
740
|
+
ConditionOperator: MetadataConditionOperator.Equals,
|
|
741
|
+
// A condition value is { Type, Value }
|
|
742
|
+
Value: { Type: "System.Boolean", Value: "true" },
|
|
743
|
+
},
|
|
744
|
+
],
|
|
505
745
|
},
|
|
506
|
-
"
|
|
507
|
-
|
|
746
|
+
Properties: { AllProperties: false, PropertyNames: ["LogicalName", "DisplayName"] },
|
|
747
|
+
};
|
|
508
748
|
|
|
749
|
+
// Initial query: the metadata, plus a version stamp
|
|
750
|
+
const result = await metadataClient.retrieveMetadataChanges(query);
|
|
509
751
|
console.log("Entities:", result.EntityMetadata);
|
|
510
752
|
console.log("Server version:", result.ServerVersionStamp);
|
|
511
753
|
|
|
512
|
-
// Later:
|
|
754
|
+
// Later: only what changed since the stamp, including deleted tables
|
|
513
755
|
const changes = await metadataClient.retrieveMetadataChanges(
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
Conditions: [{ PropertyName: "IsCustomEntity", Value: true }],
|
|
518
|
-
},
|
|
519
|
-
Properties: {
|
|
520
|
-
PropertyNames: ["LogicalName", "DisplayName"],
|
|
521
|
-
},
|
|
522
|
-
},
|
|
523
|
-
"Entity",
|
|
524
|
-
result.ServerVersionStamp // Pass previous stamp
|
|
756
|
+
query,
|
|
757
|
+
result.ServerVersionStamp, // the previous stamp
|
|
758
|
+
"Default" // DeletedMetadataFilters: "Default" (tables), "Attribute", "All", or a comma-separated list
|
|
525
759
|
);
|
|
526
|
-
|
|
527
760
|
console.log("Changed entities:", changes.EntityMetadata);
|
|
528
|
-
console.log("Deleted
|
|
761
|
+
console.log("Deleted metadata:", changes.DeletedMetadata);
|
|
529
762
|
```
|
|
530
763
|
|
|
764
|
+
Enum members in the query are sent by name (`"And"`, `"Equals"`), the only form Dataverse
|
|
765
|
+
accepts.
|
|
766
|
+
|
|
531
767
|
---
|
|
532
768
|
|
|
533
769
|
## Important Notes
|
|
534
770
|
|
|
535
771
|
### Update Operations Use PUT
|
|
536
772
|
|
|
537
|
-
Per Microsoft documentation, all metadata update operations use HTTP PUT (not PATCH).
|
|
773
|
+
Per Microsoft documentation, all metadata update operations use HTTP PUT (not PATCH). A PUT
|
|
774
|
+
replaces the definition, so a property you leave out is reset to its default. The `update*`
|
|
775
|
+
methods therefore take the complete definition, with its `@odata.type`; a body of just the
|
|
776
|
+
changed properties doesn't compile. Retrieve the definition, change it and send all of it back:
|
|
538
777
|
|
|
539
778
|
```typescript
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
LocalizedLabels: [{ Label: "Updated description", LanguageCode: 1033 }],
|
|
548
|
-
},
|
|
549
|
-
// Include all other properties you want to preserve
|
|
779
|
+
const note = await metadataClient.getAttributeDefinition("new_project", "new_note", {
|
|
780
|
+
cast: "StringAttributeMetadata", // returns the type's own properties, such as MaxLength
|
|
781
|
+
});
|
|
782
|
+
await metadataClient.updateAttributeDefinition("new_project", "new_note", {
|
|
783
|
+
...note,
|
|
784
|
+
"@odata.type": "Microsoft.Dynamics.CRM.StringAttributeMetadata",
|
|
785
|
+
MaxLength: 500,
|
|
550
786
|
});
|
|
551
787
|
```
|
|
552
788
|
|
|
789
|
+
Or let a `mergeAndUpdate*` method do that: it retrieves the current definition (with
|
|
790
|
+
`Consistency: Strong`, so not a cached copy that would undo a change made seconds before), applies
|
|
791
|
+
your changes and sends the result. Columns and relationships name their `@odata.type`, which the
|
|
792
|
+
retrieve casts to:
|
|
793
|
+
|
|
794
|
+
```typescript
|
|
795
|
+
// Only the changed properties: the SDK fills in the rest from the current definition
|
|
796
|
+
await metadataClient.mergeAndUpdateEntityDefinition("new_project", {
|
|
797
|
+
DisplayName: { LocalizedLabels: [{ Label: "Updated Project", LanguageCode: 1033 }] },
|
|
798
|
+
Description: { LocalizedLabels: [{ Label: "Updated description", LanguageCode: 1033 }] },
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
await metadataClient.mergeAndUpdateAttributeDefinition("new_project", "new_note", {
|
|
802
|
+
"@odata.type": "Microsoft.Dynamics.CRM.StringAttributeMetadata",
|
|
803
|
+
DisplayName: { LocalizedLabels: [{ Label: "Comment", LanguageCode: 1033 }] },
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
await metadataClient.mergeAndUpdateRelationship("new_account_project", {
|
|
807
|
+
"@odata.type": "Microsoft.Dynamics.CRM.OneToManyRelationshipMetadata",
|
|
808
|
+
IsValidForAdvancedFind: false,
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
await metadataClient.mergeAndUpdateGlobalOptionSet("new_colors", {
|
|
812
|
+
Description: { LocalizedLabels: [{ Label: "Paint colors", LanguageCode: 1033 }] },
|
|
813
|
+
});
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
A `PUT` doesn't change a choice's options: use `insertOptionValue()`, `updateOptionValue()`,
|
|
817
|
+
`deleteOptionValue()` and `orderOptions()` for those. `mergeAndUpdateGlobalOptionSet()` leaves
|
|
818
|
+
the options out of the `PUT`.
|
|
819
|
+
|
|
553
820
|
The SDK automatically sets the `MSCRM.MergeLabels: true` header to merge labels instead of replacing them, preserving labels for other languages.
|
|
554
821
|
|
|
555
822
|
### Strict Typing
|
|
@@ -557,10 +824,36 @@ The SDK automatically sets the `MSCRM.MergeLabels: true` header to merge labels
|
|
|
557
824
|
The metadata package uses strict type separation:
|
|
558
825
|
|
|
559
826
|
- **Read types** - include all properties returned from the server (e.g., `EntityMetadata`)
|
|
560
|
-
- **Create types** - exclude read-only and internal properties (e.g., `EntityMetadataCreate`)
|
|
561
|
-
- **Update types** -
|
|
827
|
+
- **Create types** - exclude read-only and internal properties, and require what a create needs (e.g., `EntityMetadataCreate`, `StringAttributeMetadataCreate`)
|
|
828
|
+
- **Update types** - the complete definition with `@odata.type` required (e.g., `EntityMetadataUpdate`, `AttributeMetadataUpdateTypes`); a retrieved definition plus its `@odata.type` can be passed back
|
|
829
|
+
- **Changes types** - only the properties to change, for the `mergeAndUpdate*` methods (e.g., `EntityMetadataChanges`, `AttributeMetadataChanges`)
|
|
830
|
+
|
|
831
|
+
This ensures you cannot accidentally send read-only fields like `MetadataId`, `ObjectTypeCode`, or internal-use fields in create/update operations. The create types leave out every property Dataverse sets itself, such as a date column's `MinSupportedValue` and `MaxSupportedValue`, a text column's `DatabaseLength`, `AttributeType` (the type comes from `@odata.type`) and `IsValidForRead`.
|
|
832
|
+
|
|
833
|
+
Column creates take `AttributeMetadataCreateTypes`, a union discriminated on `@odata.type`
|
|
834
|
+
(`"Microsoft.Dynamics.CRM.StringAttributeMetadata"`, …), so the type-specific properties
|
|
835
|
+
(`MaxLength`, `MinValue`, …) are checked for the type you name. Column, relationship
|
|
836
|
+
and global choice creates require `@odata.type`; for a table it's optional. A lookup column isn't
|
|
837
|
+
one of them: it is created with its relationship, as the `Lookup` of
|
|
838
|
+
`createOneToManyRelationship()`.
|
|
839
|
+
|
|
840
|
+
A table create (`EntityMetadataCreate`) requires what Dataverse needs: `SchemaName`,
|
|
841
|
+
`DisplayName`, `DisplayCollectionName`, `OwnershipType`, `IsActivity`, `HasActivities`, `HasNotes`
|
|
842
|
+
and the primary name column. `Attributes` is a tuple whose first element is that column
|
|
843
|
+
(`PrimaryNameAttributeMetadataCreate`: a text column with `IsPrimaryName: true`); the other
|
|
844
|
+
columns follow and can't be a second primary name.
|
|
562
845
|
|
|
563
|
-
|
|
846
|
+
The package exports the metadata types only: the definitions, their create and update payloads,
|
|
847
|
+
and the parameters and responses of the metadata operations `MetadataClient` calls. Other
|
|
848
|
+
operations' types are in the actions, functions and messages packages.
|
|
849
|
+
|
|
850
|
+
### Enums Are Member Names
|
|
851
|
+
|
|
852
|
+
The Web API reads and writes enum members by name (`"UserOwned"`, `"ApplicationRequired"`); a
|
|
853
|
+
number such as `OwnershipType: 1` is rejected. The exported enums (`OwnershipTypes`,
|
|
854
|
+
`AttributeRequiredLevel`, `StringFormat`, …) are string-valued, so `OwnershipTypes.UserOwned` is
|
|
855
|
+
`"UserOwned"` and you can write either form. Read types use the same names, so
|
|
856
|
+
`entity.OwnershipType === OwnershipTypes.UserOwned` works on a retrieved definition.
|
|
564
857
|
|
|
565
858
|
---
|
|
566
859
|
|
|
@@ -584,17 +877,13 @@ const entityId = await metadataClient.createEntityDefinition({
|
|
|
584
877
|
|
|
585
878
|
---
|
|
586
879
|
|
|
587
|
-
##
|
|
588
|
-
|
|
589
|
-
This package is part of the `dataverse-sdk-typescript` monorepo and is licensed under **GNU AGPL v3.0**. See [LICENSE](../../LICENSE) for details.
|
|
590
|
-
|
|
591
|
-
### Publishing Changes
|
|
880
|
+
## Publishing Changes
|
|
592
881
|
|
|
593
882
|
Metadata changes must be published before they take effect in the application. Use PublishXml or PublishAllXml actions from the messages package.
|
|
594
883
|
|
|
595
884
|
## License
|
|
596
885
|
|
|
597
|
-
GNU AGPL v3.0
|
|
886
|
+
This package is part of the `dataverse-sdk-typescript` monorepo and is licensed under **GNU AGPL v3.0**. See [LICENSE](../../LICENSE) for details.
|
|
598
887
|
|
|
599
888
|
## Related Packages
|
|
600
889
|
|