@khester/create-dynamics-app 2.1.0 → 2.2.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.
Files changed (121) hide show
  1. package/dist/artifacts/registry.d.ts +4 -3
  2. package/dist/artifacts/registry.d.ts.map +1 -1
  3. package/dist/artifacts/registry.js +121 -11
  4. package/dist/artifacts/registry.js.map +1 -1
  5. package/dist/artifacts/types.d.ts +1 -1
  6. package/dist/artifacts/types.d.ts.map +1 -1
  7. package/dist/index.js +2 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/injectDevTools.d.ts.map +1 -1
  10. package/dist/injectDevTools.js +4 -2
  11. package/dist/injectDevTools.js.map +1 -1
  12. package/dist/scaffold.d.ts +1 -0
  13. package/dist/scaffold.d.ts.map +1 -1
  14. package/dist/scaffold.js +3 -1
  15. package/dist/scaffold.js.map +1 -1
  16. package/package.json +3 -2
  17. package/templates/grid-starter/ARCHITECTURE.md +66 -0
  18. package/templates/grid-starter/README.md +122 -0
  19. package/templates/grid-starter/env.example +16 -0
  20. package/templates/grid-starter/gitignore +6 -0
  21. package/templates/grid-starter/index.html +16 -0
  22. package/templates/grid-starter/package.json +39 -0
  23. package/templates/grid-starter/src/App.tsx +23 -0
  24. package/templates/grid-starter/src/core/services/FetchApiService.ts +117 -0
  25. package/templates/grid-starter/src/core/services/IApiService.ts +37 -0
  26. package/templates/grid-starter/src/core/services/MockApiService.ts +72 -0
  27. package/templates/grid-starter/src/core/services/ServiceFactory.ts +58 -0
  28. package/templates/grid-starter/src/core/services/XrmApiService.ts +135 -0
  29. package/templates/grid-starter/src/core/services/crudLogging.ts +52 -0
  30. package/templates/grid-starter/src/dev-tools/DevPanel.tsx +239 -0
  31. package/templates/grid-starter/src/grid/GridPage.tsx +119 -0
  32. package/templates/grid-starter/src/index.tsx +18 -0
  33. package/templates/grid-starter/src/vite-env.d.ts +15 -0
  34. package/templates/grid-starter/tools/deploy/deploy-webresource.cjs +117 -0
  35. package/templates/grid-starter/tsconfig.json +19 -0
  36. package/templates/grid-starter/vite.config.ts +76 -0
  37. package/templates/pcf-field/_variants/ValueInput.boolean.tsx +2 -0
  38. package/templates/pcf-field/_variants/ValueInput.date.tsx +2 -0
  39. package/templates/pcf-field/_variants/ValueInput.number.tsx +2 -0
  40. package/templates/pcf-field/_variants/ValueInput.optionset.tsx +77 -0
  41. package/templates/pcf-field/_variants/ValueInput.text.tsx +2 -0
  42. package/templates/pcf-field/index.ts +1 -1
  43. package/templates/pcf-field/package.json +3 -1
  44. package/templates/pcf-field/{{componentName}}Component.tsx +2 -0
  45. package/templates/react-custom-page/ARCHITECTURE.md +75 -0
  46. package/templates/react-custom-page/README.md +74 -568
  47. package/templates/react-custom-page/env.example +16 -0
  48. package/templates/react-custom-page/gitignore +1 -0
  49. package/templates/react-custom-page/index.html +16 -0
  50. package/templates/react-custom-page/package.json +21 -49
  51. package/templates/react-custom-page/src/App.tsx +26 -0
  52. package/templates/react-custom-page/src/core/recordContext.test.ts +30 -0
  53. package/templates/react-custom-page/src/core/recordContext.ts +51 -0
  54. package/templates/react-custom-page/src/core/services/FetchApiService.ts +117 -0
  55. package/templates/react-custom-page/src/core/services/IApiService.ts +37 -0
  56. package/templates/react-custom-page/src/core/services/MockApiService.ts +73 -0
  57. package/templates/react-custom-page/src/core/services/ServiceFactory.ts +58 -0
  58. package/templates/react-custom-page/src/core/services/XrmApiService.ts +135 -0
  59. package/templates/react-custom-page/src/core/services/crudLogging.ts +52 -0
  60. package/templates/react-custom-page/src/dev-tools/DevPanel.tsx +238 -0
  61. package/templates/react-custom-page/src/domain/diff.test.ts +87 -0
  62. package/templates/react-custom-page/src/domain/diff.ts +38 -0
  63. package/templates/react-custom-page/src/example/ExamplePage.tsx +140 -0
  64. package/templates/react-custom-page/src/example/exampleError.ts +36 -0
  65. package/templates/react-custom-page/src/example/hooks/useExampleData.ts +40 -0
  66. package/templates/react-custom-page/src/example/hooks/useExampleForm.ts +99 -0
  67. package/templates/react-custom-page/src/example/mappers/accountMapper.test.ts +38 -0
  68. package/templates/react-custom-page/src/example/mappers/accountMapper.ts +55 -0
  69. package/templates/react-custom-page/src/example/models/Account.ts +74 -0
  70. package/templates/react-custom-page/src/index.tsx +18 -128
  71. package/templates/react-custom-page/src/vite-env.d.ts +15 -0
  72. package/templates/react-custom-page/tools/deploy/deploy-webresource.cjs +117 -0
  73. package/templates/react-custom-page/tsconfig.json +12 -22
  74. package/templates/react-custom-page/vite.config.ts +76 -0
  75. package/templates/starter-page/README.md +38 -0
  76. package/templates/starter-page/_variants/App.dashboard.v8.tsx +46 -0
  77. package/templates/starter-page/_variants/App.form.v8.tsx +59 -0
  78. package/templates/starter-page/_variants/App.master-detail.v8.tsx +61 -0
  79. package/templates/starter-page/_variants/App.panel.v8.tsx +99 -0
  80. package/templates/starter-page/gitignore +5 -0
  81. package/templates/starter-page/package.json +27 -0
  82. package/templates/starter-page/public/index.html +11 -0
  83. package/templates/starter-page/src/index.tsx +10 -0
  84. package/templates/starter-page/src/services/dataverse.ts +30 -0
  85. package/templates/starter-page/tsconfig.json +15 -0
  86. package/templates/starter-page/webpack.config.js +17 -0
  87. package/templates/react-custom-page/deployment/README.md +0 -484
  88. package/templates/react-custom-page/docs/ARCHITECTURE_OVERVIEW.md +0 -506
  89. package/templates/react-custom-page/docs/BEST_PRACTICES.md +0 -723
  90. package/templates/react-custom-page/docs/MIGRATION_GUIDE.md +0 -447
  91. package/templates/react-custom-page/public/index.html +0 -15
  92. package/templates/react-custom-page/scripts/custom-build.js +0 -255
  93. package/templates/react-custom-page/src/components/AccountForm.css +0 -71
  94. package/templates/react-custom-page/src/components/AccountForm.tsx +0 -541
  95. package/templates/react-custom-page/src/components/AccountManagement.css +0 -86
  96. package/templates/react-custom-page/src/components/AccountManagement.tsx +0 -370
  97. package/templates/react-custom-page/src/components/ContactForm.css +0 -48
  98. package/templates/react-custom-page/src/components/ContactForm.tsx +0 -327
  99. package/templates/react-custom-page/src/components/ContactManagement.css +0 -86
  100. package/templates/react-custom-page/src/components/ContactManagement.tsx +0 -357
  101. package/templates/react-custom-page/src/components/Logging/LogDialog.tsx +0 -291
  102. package/templates/react-custom-page/src/components/Logging/LoggingContext.tsx +0 -166
  103. package/templates/react-custom-page/src/components/Logging/LoggingDebugPanel.css +0 -192
  104. package/templates/react-custom-page/src/components/Logging/LoggingDebugPanel.tsx +0 -177
  105. package/templates/react-custom-page/src/components/Logging/LoggingProvider.tsx +0 -3
  106. package/templates/react-custom-page/src/components/Logging/logger.ts +0 -193
  107. package/templates/react-custom-page/src/constants/account.ts +0 -410
  108. package/templates/react-custom-page/src/constants/contact.ts +0 -362
  109. package/templates/react-custom-page/src/models/Account.ts +0 -480
  110. package/templates/react-custom-page/src/models/BaseEntity.ts +0 -204
  111. package/templates/react-custom-page/src/models/Contact.ts +0 -580
  112. package/templates/react-custom-page/src/pcf/ContactControlWrapper.tsx +0 -107
  113. package/templates/react-custom-page/src/pcf/MultiEntityControlWrapper.tsx +0 -205
  114. package/templates/react-custom-page/src/providers/DynamicsProvider.tsx +0 -353
  115. package/templates/react-custom-page/src/services/MockApiService.ts +0 -260
  116. package/templates/react-custom-page/src/services/ServiceFactory.ts +0 -65
  117. package/templates/react-custom-page/src/services/XrmApiService.ts +0 -213
  118. package/templates/react-custom-page/src/styles/index.css +0 -171
  119. package/templates/react-custom-page/tools/metadata-sync/index.js +0 -152
  120. package/templates/react-custom-page/webpack.config.js +0 -57
  121. /package/templates/_shared/dev-tools/auth/{get-token.js → get-token.cjs} +0 -0
@@ -0,0 +1,38 @@
1
+ # {{projectName}}
2
+
3
+ A Dynamics 365 **starter page** — a React web resource built on a single
4
+ [`@dataverse-kit/surface-kit`](https://www.npmjs.com/package/@dataverse-kit/surface-kit)
5
+ surface (Fluent UI v8), hosted as a custom page / web resource.
6
+
7
+ The surface was chosen at scaffold time (`--surface <form|panel|dashboard|master-detail>`)
8
+ and lives in `src/App.tsx`:
9
+
10
+ | Surface | Renders | Extra dep |
11
+ |---------|---------|-----------|
12
+ | `form` | `FormSurface` + `Section`s + `FieldRow`s (a record edit page) | — |
13
+ | `panel` | A list with a slide-in `PanelSurface` editor | — |
14
+ | `dashboard` | `DashboardSurface` tile grid hosting a `<DataGrid>` | `@dataverse-kit/grid-kit` |
15
+ | `master-detail` | `MasterDetailSurface` (list + detail split) hosting a `<DataGrid>` | `@dataverse-kit/grid-kit` |
16
+
17
+ ## Develop
18
+
19
+ ```bash
20
+ npm install
21
+ npm run dev # webpack dev server on http://localhost:8080
22
+ npm run build # production bundle → dist/{{projectName}}.js
23
+ npm run typecheck # tsc --noEmit
24
+ ```
25
+
26
+ Data access goes through `src/services/dataverse.ts` (`Xrm.WebApi`). Outside a
27
+ model-driven host, stub `Xrm` with the `serve` dev-tool's `tools/dev/mock-xrm.js`.
28
+
29
+ ## Dev-tools
30
+
31
+ ```bash
32
+ npm run auth:token -- --url https://YOUR_ORG.crm.dynamics.com # token → .env
33
+ ```
34
+
35
+ ## Deploy
36
+
37
+ Upload `dist/{{projectName}}.js` as a web resource and host it on a custom page or
38
+ open it from a form / ribbon command.
@@ -0,0 +1,46 @@
1
+ import * as React from 'react';
2
+ import { ThemeProvider } from '@fluentui/react/lib/Theme';
3
+ import { DashboardSurface, Section } from '@dataverse-kit/surface-kit';
4
+ import { DataGrid, type ColumnDef } from '@dataverse-kit/grid-kit';
5
+
6
+ /**
7
+ * Starter page — `dashboard` surface. A surface-kit `DashboardSurface` tile grid
8
+ * (2 cols, reflows to 1 on narrow widths) whose tiles host grid-kit `<DataGrid>`s.
9
+ * Swap the sample rows for `retrieveMultiple(...)` from `./services/dataverse`.
10
+ */
11
+ interface AccountRow extends Record<string, unknown> {
12
+ key: string;
13
+ name: string;
14
+ revenue: number;
15
+ status: string;
16
+ }
17
+
18
+ const COLUMNS: ColumnDef<AccountRow>[] = [
19
+ { key: 'name', fieldName: 'name', name: 'Account', rendererType: 'text' },
20
+ { key: 'revenue', fieldName: 'revenue', name: 'Revenue', rendererType: 'currency' },
21
+ { key: 'status', fieldName: 'status', name: 'Status', rendererType: 'text' },
22
+ ];
23
+
24
+ const ROWS: AccountRow[] = [
25
+ { key: '1', name: 'Contoso', revenue: 5_000_000, status: 'Active' },
26
+ { key: '2', name: 'Fabrikam', revenue: 2_400_000, status: 'Active' },
27
+ { key: '3', name: 'Adventure Works', revenue: 980_000, status: 'On hold' },
28
+ ];
29
+
30
+ export const App: React.FC = () => {
31
+ return (
32
+ <ThemeProvider>
33
+ <DashboardSurface
34
+ columns={2}
35
+ tiles={[
36
+ <Section key="accounts" title="Top accounts" variant="card">
37
+ <DataGrid<AccountRow> items={ROWS} columns={COLUMNS} />
38
+ </Section>,
39
+ <Section key="pipeline" title="Pipeline" variant="card">
40
+ <DataGrid<AccountRow> items={ROWS} columns={COLUMNS} />
41
+ </Section>,
42
+ ]}
43
+ />
44
+ </ThemeProvider>
45
+ );
46
+ };
@@ -0,0 +1,59 @@
1
+ import * as React from 'react';
2
+ import { ThemeProvider } from '@fluentui/react/lib/Theme';
3
+ import { Stack } from '@fluentui/react/lib/Stack';
4
+ import { TextField } from '@fluentui/react/lib/TextField';
5
+ import { PrimaryButton, DefaultButton } from '@fluentui/react/lib/Button';
6
+ import { FormSurface, Section, FieldRow } from '@dataverse-kit/surface-kit';
7
+ import { createRecord } from './services/dataverse';
8
+
9
+ /**
10
+ * Starter page — `form` surface. A surface-kit `FormSurface` hosts a `Section` of
11
+ * `FieldRow`s; the layout stacks to one column on narrow widths (surface-kit owns
12
+ * the responsive breakpoints). Saves a contact via `Xrm.WebApi`.
13
+ */
14
+ export const App: React.FC = () => {
15
+ const [name, setName] = React.useState('');
16
+ const [email, setEmail] = React.useState('');
17
+ const [phone, setPhone] = React.useState('');
18
+ const [saving, setSaving] = React.useState(false);
19
+
20
+ const onSave = async () => {
21
+ setSaving(true);
22
+ try {
23
+ await createRecord('contact', {
24
+ fullname: name,
25
+ emailaddress1: email,
26
+ telephone1: phone,
27
+ });
28
+ alert('Saved.');
29
+ } catch (e) {
30
+ alert(`Save failed (Xrm not available here?): ${String(e)}`);
31
+ } finally {
32
+ setSaving(false);
33
+ }
34
+ };
35
+
36
+ return (
37
+ <ThemeProvider>
38
+ <FormSurface maxWidth={960}>
39
+ <Section title="{{projectName}}" columnSpan={12}>
40
+ <FieldRow label="Full name" required>
41
+ <TextField value={name} onChange={(_, v) => setName(v ?? '')} />
42
+ </FieldRow>
43
+ <FieldRow label="Email">
44
+ <TextField value={email} onChange={(_, v) => setEmail(v ?? '')} />
45
+ </FieldRow>
46
+ <FieldRow label="Phone">
47
+ <TextField value={phone} onChange={(_, v) => setPhone(v ?? '')} />
48
+ </FieldRow>
49
+ <FieldRow>
50
+ <Stack horizontal tokens={{ childrenGap: 8 }}>
51
+ <PrimaryButton text="Save" onClick={onSave} disabled={saving} />
52
+ <DefaultButton text="Cancel" onClick={() => window.history.back()} />
53
+ </Stack>
54
+ </FieldRow>
55
+ </Section>
56
+ </FormSurface>
57
+ </ThemeProvider>
58
+ );
59
+ };
@@ -0,0 +1,61 @@
1
+ import * as React from 'react';
2
+ import { ThemeProvider } from '@fluentui/react/lib/Theme';
3
+ import { Text } from '@fluentui/react/lib/Text';
4
+ import { MasterDetailSurface, Section, FieldRow } from '@dataverse-kit/surface-kit';
5
+ import { DataGrid, type ColumnDef } from '@dataverse-kit/grid-kit';
6
+
7
+ /**
8
+ * Starter page — `master-detail` surface. A surface-kit `MasterDetailSurface`
9
+ * (list + detail split that stacks below the `md` breakpoint) with a grid-kit
10
+ * `<DataGrid>` master and a `Section` detail driven by the selected row. Swap the
11
+ * sample rows for `retrieveMultiple(...)` from `./services/dataverse`.
12
+ */
13
+ interface AccountRow extends Record<string, unknown> {
14
+ key: string;
15
+ name: string;
16
+ city: string;
17
+ owner: string;
18
+ }
19
+
20
+ const COLUMNS: ColumnDef<AccountRow>[] = [
21
+ { key: 'name', fieldName: 'name', name: 'Account', rendererType: 'text' },
22
+ { key: 'city', fieldName: 'city', name: 'City', rendererType: 'text' },
23
+ ];
24
+
25
+ const ROWS: AccountRow[] = [
26
+ { key: '1', name: 'Contoso', city: 'Seattle', owner: 'Dana' },
27
+ { key: '2', name: 'Fabrikam', city: 'Austin', owner: 'Lee' },
28
+ { key: '3', name: 'Adventure Works', city: 'Denver', owner: 'Sam' },
29
+ ];
30
+
31
+ export const App: React.FC = () => {
32
+ const [selected, setSelected] = React.useState<AccountRow | undefined>(ROWS[0]);
33
+
34
+ return (
35
+ <ThemeProvider>
36
+ <MasterDetailSurface
37
+ master={
38
+ <DataGrid<AccountRow>
39
+ items={ROWS}
40
+ columns={COLUMNS}
41
+ selectionMode="single"
42
+ onSelectionChanged={(rows) => setSelected(rows[0])}
43
+ />
44
+ }
45
+ detail={
46
+ selected ? (
47
+ <Section title={selected.name} variant="flat">
48
+ <FieldRow label="City">
49
+ <Text>{selected.city}</Text>
50
+ </FieldRow>
51
+ <FieldRow label="Owner">
52
+ <Text>{selected.owner}</Text>
53
+ </FieldRow>
54
+ </Section>
55
+ ) : null
56
+ }
57
+ detailFallback={<Text>Select an account to see its details.</Text>}
58
+ />
59
+ </ThemeProvider>
60
+ );
61
+ };
@@ -0,0 +1,99 @@
1
+ import * as React from 'react';
2
+ import { ThemeProvider } from '@fluentui/react/lib/Theme';
3
+ import { Stack } from '@fluentui/react/lib/Stack';
4
+ import { TextField } from '@fluentui/react/lib/TextField';
5
+ import { PrimaryButton, DefaultButton } from '@fluentui/react/lib/Button';
6
+ import { DetailsList, SelectionMode, type IColumn } from '@fluentui/react/lib/DetailsList';
7
+ import { FormSurface, Section, FieldRow, PanelSurface } from '@dataverse-kit/surface-kit';
8
+ import { createRecord } from './services/dataverse';
9
+
10
+ /**
11
+ * Starter page — `panel` surface. A list with a slide-in surface-kit `PanelSurface`
12
+ * editor (Fluent v8 Panel, width-derived PanelType, sticky footer): the Section's
13
+ * command bar opens the panel to create a record, which is saved via `Xrm.WebApi`.
14
+ * The list is a plain Fluent `DetailsList` (no grid-kit) — swap the sample rows for
15
+ * `retrieveMultiple(...)` from `./services/dataverse`.
16
+ */
17
+ interface ContactRow {
18
+ key: string;
19
+ name: string;
20
+ email: string;
21
+ }
22
+
23
+ const COLUMNS: IColumn[] = [
24
+ { key: 'name', name: 'Name', fieldName: 'name', minWidth: 120, maxWidth: 240, isResizable: true },
25
+ { key: 'email', name: 'Email', fieldName: 'email', minWidth: 160, isResizable: true },
26
+ ];
27
+
28
+ const SAMPLE: ContactRow[] = [
29
+ { key: '1', name: 'Dana Reed', email: 'dana@contoso.com' },
30
+ { key: '2', name: 'Lee Park', email: 'lee@fabrikam.com' },
31
+ ];
32
+
33
+ export const App: React.FC = () => {
34
+ const [open, setOpen] = React.useState(false);
35
+ const [name, setName] = React.useState('');
36
+ const [email, setEmail] = React.useState('');
37
+ const [saving, setSaving] = React.useState(false);
38
+
39
+ const onSave = async () => {
40
+ setSaving(true);
41
+ try {
42
+ await createRecord('contact', { fullname: name, emailaddress1: email });
43
+ setOpen(false);
44
+ setName('');
45
+ setEmail('');
46
+ } catch (e) {
47
+ alert(`Save failed (Xrm not available here?): ${String(e)}`);
48
+ } finally {
49
+ setSaving(false);
50
+ }
51
+ };
52
+
53
+ return (
54
+ <ThemeProvider>
55
+ <FormSurface maxWidth={960}>
56
+ <Section
57
+ title="{{projectName}}"
58
+ columnSpan={12}
59
+ showCommandBar
60
+ commandBarItems={[
61
+ {
62
+ key: 'new',
63
+ text: 'New record',
64
+ iconProps: { iconName: 'Add' },
65
+ onClick: () => setOpen(true),
66
+ },
67
+ ]}
68
+ >
69
+ <DetailsList
70
+ items={SAMPLE}
71
+ columns={COLUMNS}
72
+ selectionMode={SelectionMode.none}
73
+ setKey="contacts"
74
+ />
75
+ </Section>
76
+ </FormSurface>
77
+
78
+ <PanelSurface
79
+ isOpen={open}
80
+ onDismiss={() => setOpen(false)}
81
+ title="New contact"
82
+ width={480}
83
+ footer={
84
+ <Stack horizontal tokens={{ childrenGap: 8 }}>
85
+ <PrimaryButton text="Save" onClick={onSave} disabled={saving} />
86
+ <DefaultButton text="Cancel" onClick={() => setOpen(false)} />
87
+ </Stack>
88
+ }
89
+ >
90
+ <FieldRow label="Full name" required>
91
+ <TextField value={name} onChange={(_, v) => setName(v ?? '')} />
92
+ </FieldRow>
93
+ <FieldRow label="Email">
94
+ <TextField value={email} onChange={(_, v) => setEmail(v ?? '')} />
95
+ </FieldRow>
96
+ </PanelSurface>
97
+ </ThemeProvider>
98
+ );
99
+ };
@@ -0,0 +1,5 @@
1
+ node_modules/
2
+ dist/
3
+ .env
4
+ .env.local
5
+ *.log
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "{{projectName}}",
3
+ "version": "1.0.0",
4
+ "description": "{{projectName}} — Dynamics 365 starter page (React, @dataverse-kit/surface-kit, Fluent v8)",
5
+ "private": true,
6
+ "scripts": {
7
+ "dev": "webpack serve --mode development",
8
+ "build": "webpack --mode production",
9
+ "typecheck": "tsc --noEmit",
10
+ "lint": "eslint src --ext .ts,.tsx"
11
+ },
12
+ "dependencies": {
13
+ "react": "^18.2.0",
14
+ "react-dom": "^18.2.0"
15
+ },
16
+ "devDependencies": {
17
+ "@types/react": "^18.2.0",
18
+ "@types/react-dom": "^18.2.0",
19
+ "@types/xrm": "^9.0.74",
20
+ "html-webpack-plugin": "^5.5.3",
21
+ "ts-loader": "^9.5.1",
22
+ "typescript": "^5.3.3",
23
+ "webpack": "^5.89.0",
24
+ "webpack-cli": "^5.1.4",
25
+ "webpack-dev-server": "^4.15.1"
26
+ }
27
+ }
@@ -0,0 +1,11 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>{{projectName}}</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ </body>
11
+ </html>
@@ -0,0 +1,10 @@
1
+ import * as React from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { App } from './App';
4
+
5
+ // On localhost, `tools/dev/mock-xrm.js` (from the `serve` dev-tool) can be loaded
6
+ // to stub the Xrm global so this resource runs outside a model-driven host.
7
+ const container = document.getElementById('root');
8
+ if (container) {
9
+ createRoot(container).render(<App />);
10
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Thin wrapper over Xrm.WebApi for this starter page's data access. The Xrm global
3
+ * is typed via @types/xrm; on localhost, stub it with tools/dev/mock-xrm.js.
4
+ */
5
+
6
+ export type DataverseRecord = Record<string, unknown>;
7
+
8
+ export async function retrieveMultiple(
9
+ entityLogicalName: string,
10
+ options = ''
11
+ ): Promise<DataverseRecord[]> {
12
+ const result = await Xrm.WebApi.retrieveMultipleRecords(entityLogicalName, options);
13
+ return result.entities;
14
+ }
15
+
16
+ export async function retrieveRecord(
17
+ entityLogicalName: string,
18
+ id: string,
19
+ options = ''
20
+ ): Promise<DataverseRecord> {
21
+ return Xrm.WebApi.retrieveRecord(entityLogicalName, id, options);
22
+ }
23
+
24
+ export async function createRecord(
25
+ entityLogicalName: string,
26
+ data: Record<string, unknown>
27
+ ): Promise<string> {
28
+ const res = await Xrm.WebApi.createRecord(entityLogicalName, data);
29
+ return res.id;
30
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2018",
4
+ "module": "ESNext",
5
+ "moduleResolution": "node",
6
+ "jsx": "react-jsx",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "skipLibCheck": true,
11
+ "lib": ["DOM", "DOM.Iterable", "ES2018"],
12
+ "types": ["xrm"]
13
+ },
14
+ "include": ["src"]
15
+ }
@@ -0,0 +1,17 @@
1
+ const path = require('path');
2
+ const HtmlWebpackPlugin = require('html-webpack-plugin');
3
+
4
+ module.exports = {
5
+ entry: './src/index.tsx',
6
+ output: {
7
+ path: path.resolve(__dirname, 'dist'),
8
+ filename: '{{projectName}}.js',
9
+ clean: true,
10
+ },
11
+ resolve: { extensions: ['.ts', '.tsx', '.js'] },
12
+ module: {
13
+ rules: [{ test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/ }],
14
+ },
15
+ plugins: [new HtmlWebpackPlugin({ template: './public/index.html' })],
16
+ devServer: { static: './dist', port: 8080, hot: true },
17
+ };