@khester/dataverse-codegen 0.1.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.
@@ -0,0 +1,241 @@
1
+ import { AttributeMetadata, EntityMetadata, IDataverseClient } from '@dynamics-ui-kit/api-client';
2
+ import { AttributeMetadataInput, EntityMetadataInput } from '@dynamics-ui-kit/entity-gen';
3
+
4
+ /**
5
+ * Adapter: raw Dataverse metadata (from @dynamics-ui-kit/api-client) →
6
+ * the `EntityMetadataInput` shape consumed by @dynamics-ui-kit/entity-gen.
7
+ *
8
+ * The two shapes are intentionally field-compatible (both PascalCase
9
+ * Dataverse-style), so this is largely a structural passthrough. It exists
10
+ * as an explicit seam so the codegen tools never depend on the api-client's
11
+ * concrete types and so we can validate/normalize in one place.
12
+ */
13
+
14
+ declare function toAttributeMetadataInput(attr: AttributeMetadata): AttributeMetadataInput;
15
+ declare function toEntityMetadataInput(entity: EntityMetadata, attributes: AttributeMetadata[]): EntityMetadataInput;
16
+
17
+ /**
18
+ * Minimal Dataverse metadata HTTP helper for the codegen tools.
19
+ *
20
+ * Used for typed-cast attribute queries the api-client doesn't expose
21
+ * (scalar detail like MaxLength/Precision). Honors 429/503 throttling with
22
+ * `Retry-After` backoff, and follows `@odata.nextLink` (metadataGetAll) so
23
+ * wide results aren't truncated. Uses the global `fetch` (Node 18+).
24
+ */
25
+ interface MetadataHttp {
26
+ /** Org base URL, e.g. https://org.crm.dynamics.com */
27
+ baseUrl: string;
28
+ token: string;
29
+ /** Web API version. Default 9.2. */
30
+ apiVersion?: string;
31
+ }
32
+ /** GET a single metadata resource (relative path), with retry/backoff. */
33
+ declare function metadataGet<T = unknown>(http: MetadataHttp, relativePath: string, maxRetries?: number): Promise<T>;
34
+ /**
35
+ * GET a metadata collection (relative path), following `@odata.nextLink` and
36
+ * concatenating every page's `value`. Use for queries that can exceed the
37
+ * server page cap (e.g. typed-cast attribute lists on very wide entities).
38
+ */
39
+ declare function metadataGetAll<T = unknown>(http: MetadataHttp, relativePath: string, maxRetries?: number): Promise<T[]>;
40
+
41
+ /**
42
+ * fetchEntityMetadata — compose a complete `EntityMetadataInput` for one
43
+ * entity from a live org, fixing the gap entity-gen's own CLI has (it calls
44
+ * a non-existent `client.getEntityMetadata`).
45
+ *
46
+ * = getEntityDefinitions(LogicalName eq '<e>')[0] + getAttributeMetadata('<e>')
47
+ * → toEntityMetadataInput()
48
+ * → (optional) typed-cast scalar augmentation (MaxLength/Precision/...)
49
+ */
50
+
51
+ interface FetchEntityMetadataOptions {
52
+ /**
53
+ * Optional extra OData filter ANDed onto the attribute query. The api-client
54
+ * always restricts to readable attributes (`IsValidForRead eq true`), so
55
+ * generated constants/models cover the readable attribute surface.
56
+ */
57
+ attributeFilter?: string;
58
+ /**
59
+ * When provided, fetch type-specific scalar detail (MaxLength, Precision,
60
+ * Min/MaxValue, Format, DateTimeBehavior) via typed-cast queries and merge
61
+ * it in. Best-effort — failures are logged and skipped.
62
+ */
63
+ augment?: MetadataHttp;
64
+ }
65
+ declare function fetchEntityMetadata(client: IDataverseClient, logicalName: string, options?: FetchEntityMetadataOptions): Promise<EntityMetadataInput>;
66
+
67
+ /**
68
+ * Typed-cast attribute augmentation.
69
+ *
70
+ * The api-client's `getAttributeMetadata` returns the base attribute shape;
71
+ * scalar detail (MaxLength, Precision, Min/MaxValue, Format, DateTimeBehavior)
72
+ * lives on type-specific subtypes and is only returned via a typed cast on the
73
+ * EntityDefinitions Attributes navigation, e.g.:
74
+ *
75
+ * EntityDefinitions(LogicalName='x')/Attributes/Microsoft.Dynamics.CRM.StringAttributeMetadata
76
+ * ?$select=LogicalName,MaxLength,Format
77
+ *
78
+ * This fills those fields in so generated constants carry full JSDoc detail.
79
+ * Best-effort: a failed cast query is skipped (codegen still succeeds, just
80
+ * with thinner comments for that type).
81
+ */
82
+
83
+ interface ScalarDetail {
84
+ LogicalName: string;
85
+ MaxLength?: number;
86
+ MinValue?: number;
87
+ MaxValue?: number;
88
+ Precision?: number;
89
+ Format?: string;
90
+ DateTimeBehavior?: {
91
+ Value: string;
92
+ };
93
+ }
94
+ type MetadataGetter = (http: MetadataHttp, path: string) => Promise<{
95
+ value: ScalarDetail[];
96
+ }>;
97
+ /**
98
+ * Merge fetched scalar detail into the attribute list (in place). Only fills
99
+ * fields that are currently unset, so an explicit value from the base fetch
100
+ * always wins. Pure — exported for testing.
101
+ */
102
+ declare function mergeScalarDetail(attrs: AttributeMetadataInput[], details: ScalarDetail[]): void;
103
+ declare function augmentScalarDetail(http: MetadataHttp, logicalName: string, attrs: AttributeMetadataInput[], get?: MetadataGetter): Promise<void>;
104
+
105
+ /**
106
+ * Non-destructive file writing — the foundational safety guarantee.
107
+ *
108
+ * Client `src/models/*` etc. are frequently hand-customized. Commands MUST
109
+ * NOT clobber existing files silently. `safeWrite`:
110
+ * - refuses to overwrite an existing file unless `force`
111
+ * - supports `dryRun` (report only) and `diff` (print a unified-ish diff)
112
+ * - creates parent dirs as needed
113
+ */
114
+ type WriteResult = 'created' | 'overwritten' | 'skipped-exists' | 'unchanged' | 'dry-run';
115
+ interface SafeWriteOptions {
116
+ force?: boolean;
117
+ dryRun?: boolean;
118
+ diff?: boolean;
119
+ }
120
+ declare function safeWrite(path: string, content: string, opts?: SafeWriteOptions): WriteResult;
121
+
122
+ /**
123
+ * Connection resolution — build an IDataverseClient from CLI flags / env,
124
+ * tolerant of the env-var prefixes client projects actually use
125
+ * (VITE_/REACT_APP_/bare DYNAMICS_), with Azure CLI as the token fallback.
126
+ *
127
+ * Token precedence: --token > env (.env / process.env) > Azure CLI (az).
128
+ * Use --token (or env) for cross-tenant client orgs where you don't have
129
+ * `az login` to the client's tenant.
130
+ */
131
+
132
+ interface ConnectionOptions {
133
+ url?: string;
134
+ token?: string;
135
+ envFile?: string;
136
+ }
137
+ interface ResolvedConnection {
138
+ client: IDataverseClient;
139
+ url: string;
140
+ /** The resolved bearer token — used for typed-cast metadata HTTP the client doesn't expose. */
141
+ token: string;
142
+ tokenSource: 'flag' | 'env' | 'azure-cli';
143
+ }
144
+ declare function resolveConnection(opts: ConnectionOptions): ResolvedConnection;
145
+
146
+ /**
147
+ * Parent-lookup (many-to-one) <link-entity> derivation for model retrieve
148
+ * scaffolds. Shared by the `models` and `design` commands.
149
+ */
150
+
151
+ interface RetrieveLink {
152
+ entity: string;
153
+ from: string;
154
+ to: string;
155
+ alias: string;
156
+ }
157
+ /**
158
+ * Derive parent-lookup joins from relationship metadata to pre-fill a model's
159
+ * `retrieveWithRelated()` scaffold. Parent lookups are relationships where THIS
160
+ * entity is the referencing (many) side — Dataverse reports both navigations as
161
+ * `OneToManyRelationship`, so we identify by `ReferencingEntity`, not type.
162
+ *
163
+ * Best-effort: returns `[]` (with a console warning) when relationships can't
164
+ * be fetched, so callers still emit a TODO scaffold.
165
+ */
166
+ declare function deriveParentLookupLinks(client: IDataverseClient, entity: EntityMetadataInput): Promise<RetrieveLink[]>;
167
+
168
+ /** Discriminator the form designer writes into every exported design file. */
169
+ declare const FORM_DESIGN_KIND = "dynamics-ui-kit.form-design";
170
+ interface DesignControl {
171
+ type?: string;
172
+ name?: string;
173
+ properties?: {
174
+ entityName?: unknown;
175
+ } & Record<string, unknown>;
176
+ dataBinding?: {
177
+ attributeLogicalName?: unknown;
178
+ lookupTargets?: unknown;
179
+ };
180
+ }
181
+ interface DesignCell {
182
+ control?: DesignControl | null;
183
+ }
184
+ interface DesignRow {
185
+ cells?: DesignCell[];
186
+ }
187
+ interface DesignSection {
188
+ rows?: DesignRow[];
189
+ }
190
+ interface DesignTab {
191
+ sections?: DesignSection[];
192
+ }
193
+ interface DesignForm {
194
+ name?: string;
195
+ type?: string;
196
+ settings?: {
197
+ dataSources?: Array<{
198
+ entityName?: unknown;
199
+ }>;
200
+ };
201
+ sections?: DesignSection[];
202
+ tabs?: DesignTab[];
203
+ }
204
+ interface FormDesignFile {
205
+ kind?: string;
206
+ schemaVersion?: number;
207
+ name?: string;
208
+ forms?: DesignForm[];
209
+ }
210
+ /** A Dataverse entity discovered in the design, with where it came from. */
211
+ interface EntityRef {
212
+ logicalName: string;
213
+ /** Human-readable provenance, for reporting (e.g. `form "Contact" (target)`). */
214
+ sources: string[];
215
+ }
216
+ interface CollectResult {
217
+ entities: EntityRef[];
218
+ /** Entity-name candidates rejected by the logical-name allowlist. */
219
+ skipped: {
220
+ name: string;
221
+ reason: string;
222
+ }[];
223
+ }
224
+ /**
225
+ * Parse + validate an exported design file. Throws on invalid JSON, an
226
+ * unexpected `kind`, or a missing `forms` array.
227
+ */
228
+ declare function parseDesignFile(raw: string): FormDesignFile;
229
+ /**
230
+ * Walk every form and collect the Dataverse entities it references:
231
+ * - each form's target entity (`settings.dataSources[0].entityName`),
232
+ * - subgrid controls' `properties.entityName`,
233
+ * - lookup controls' `dataBinding.lookupTargets`.
234
+ *
235
+ * Names are normalized to lowercase and validated against the Dataverse
236
+ * logical-name rule; invalid candidates are reported in `skipped`, never
237
+ * generated (they'd otherwise inject into metadata OData queries).
238
+ */
239
+ declare function collectEntities(design: FormDesignFile): CollectResult;
240
+
241
+ export { type CollectResult, type ConnectionOptions, type EntityRef, FORM_DESIGN_KIND, type FetchEntityMetadataOptions, type FormDesignFile, type MetadataGetter, type MetadataHttp, type ResolvedConnection, type RetrieveLink, type SafeWriteOptions, type WriteResult, augmentScalarDetail, collectEntities, deriveParentLookupLinks, fetchEntityMetadata, mergeScalarDetail, metadataGet, metadataGetAll, parseDesignFile, resolveConnection, safeWrite, toAttributeMetadataInput, toEntityMetadataInput };