@contractspec/bundle.library 3.9.8 → 3.9.9
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/.turbo/turbo-build.log +222 -214
- package/CHANGELOG.md +52 -0
- package/dist/components/docs/DocsIndexPage.js +2 -2
- package/dist/components/docs/docsManifest.js +1 -1
- package/dist/components/docs/getting-started/DataViewTutorialPage.js +81 -6
- package/dist/components/docs/getting-started/index.js +94 -19
- package/dist/components/docs/guides/GuideDataExchangeImportTemplatesPage.content.d.ts +6 -0
- package/dist/components/docs/guides/GuideDataExchangeImportTemplatesPage.content.js +176 -0
- package/dist/components/docs/guides/GuideDataExchangeImportTemplatesPage.d.ts +1 -0
- package/dist/components/docs/guides/GuideDataExchangeImportTemplatesPage.js +176 -0
- package/dist/components/docs/guides/GuidesIndexPage.js +2 -2
- package/dist/components/docs/guides/index.d.ts +1 -0
- package/dist/components/docs/guides/index.js +220 -46
- package/dist/components/docs/index.js +1003 -309
- package/dist/components/docs/libraries/LibrariesApplicationShellPage.content.d.ts +22 -5
- package/dist/components/docs/libraries/LibrariesApplicationShellPage.content.js +125 -37
- package/dist/components/docs/libraries/LibrariesApplicationShellPage.js +125 -37
- package/dist/components/docs/libraries/LibrariesDataViewsPage.js +120 -3
- package/dist/components/docs/libraries/LibrariesDesignSystemPage.js +101 -2
- package/dist/components/docs/libraries/LibrariesOverviewPage.js +1 -1
- package/dist/components/docs/libraries/LibrariesPersonalizationPage.js +58 -4
- package/dist/components/docs/libraries/LibrariesTranslationRuntimePage.content.d.ts +10 -0
- package/dist/components/docs/libraries/LibrariesTranslationRuntimePage.content.js +43 -0
- package/dist/components/docs/libraries/LibrariesTranslationRuntimePage.d.ts +1 -0
- package/dist/components/docs/libraries/LibrariesTranslationRuntimePage.js +43 -0
- package/dist/components/docs/libraries/index.d.ts +1 -0
- package/dist/components/docs/libraries/index.js +496 -97
- package/dist/components/docs/specs/SpecsDataViewsPage.js +49 -3
- package/dist/components/docs/specs/index.js +60 -14
- package/dist/index.js +1014 -320
- package/dist/node/components/docs/DocsIndexPage.js +2 -2
- package/dist/node/components/docs/docsManifest.js +1 -1
- package/dist/node/components/docs/getting-started/DataViewTutorialPage.js +81 -6
- package/dist/node/components/docs/getting-started/index.js +94 -19
- package/dist/node/components/docs/guides/GuideDataExchangeImportTemplatesPage.content.js +175 -0
- package/dist/node/components/docs/guides/GuideDataExchangeImportTemplatesPage.js +175 -0
- package/dist/node/components/docs/guides/GuidesIndexPage.js +2 -2
- package/dist/node/components/docs/guides/index.js +220 -46
- package/dist/node/components/docs/index.js +1003 -309
- package/dist/node/components/docs/libraries/LibrariesApplicationShellPage.content.js +125 -37
- package/dist/node/components/docs/libraries/LibrariesApplicationShellPage.js +125 -37
- package/dist/node/components/docs/libraries/LibrariesDataViewsPage.js +120 -3
- package/dist/node/components/docs/libraries/LibrariesDesignSystemPage.js +101 -2
- package/dist/node/components/docs/libraries/LibrariesOverviewPage.js +1 -1
- package/dist/node/components/docs/libraries/LibrariesPersonalizationPage.js +58 -4
- package/dist/node/components/docs/libraries/LibrariesTranslationRuntimePage.content.js +42 -0
- package/dist/node/components/docs/libraries/LibrariesTranslationRuntimePage.js +42 -0
- package/dist/node/components/docs/libraries/index.js +496 -97
- package/dist/node/components/docs/specs/SpecsDataViewsPage.js +49 -3
- package/dist/node/components/docs/specs/index.js +60 -14
- package/dist/node/index.js +1014 -320
- package/package.json +74 -26
- package/src/components/docs/docsManifest.test.ts +87 -0
- package/src/components/docs/docsManifest.ts +90 -3
- package/src/components/docs/generated/docs-index.notifications.json +7 -7
- package/src/components/docs/getting-started/DataViewTutorialPage.tsx +181 -50
- package/src/components/docs/guides/GuideDataExchangeImportTemplatesPage.content.ts +185 -0
- package/src/components/docs/guides/GuideDataExchangeImportTemplatesPage.tsx +186 -0
- package/src/components/docs/guides/GuidesIndexPage.tsx +49 -42
- package/src/components/docs/guides/index.ts +1 -0
- package/src/components/docs/libraries/LibrariesApplicationShellPage.content.ts +148 -35
- package/src/components/docs/libraries/LibrariesApplicationShellPage.tsx +38 -5
- package/src/components/docs/libraries/LibrariesDataViewsPage.tsx +267 -64
- package/src/components/docs/libraries/LibrariesDesignSystemPage.tsx +235 -0
- package/src/components/docs/libraries/LibrariesOverviewPage.tsx +8 -2
- package/src/components/docs/libraries/LibrariesPersonalizationPage.tsx +141 -31
- package/src/components/docs/libraries/LibrariesTranslationRuntimePage.content.ts +78 -0
- package/src/components/docs/libraries/LibrariesTranslationRuntimePage.tsx +137 -0
- package/src/components/docs/libraries/index.ts +1 -0
- package/src/components/docs/specs/SpecsDataViewsPage.tsx +239 -113
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import { CodeBlock } from '@contractspec/lib.design-system';
|
|
2
|
+
import { HStack, VStack } from '@contractspec/lib.design-system/layout';
|
|
3
|
+
import { List, ListItem } from '@contractspec/lib.design-system/list';
|
|
4
|
+
import {
|
|
5
|
+
H1,
|
|
6
|
+
H2,
|
|
7
|
+
H3,
|
|
8
|
+
P,
|
|
9
|
+
Text,
|
|
10
|
+
} from '@contractspec/lib.design-system/typography';
|
|
2
11
|
import Link from '@contractspec/lib.ui-link';
|
|
3
12
|
import { ChevronRight } from 'lucide-react';
|
|
4
13
|
|
|
@@ -64,8 +73,41 @@ export const TransactionHistory = defineDataView({
|
|
|
64
73
|
dataPath: 'processingMinutes',
|
|
65
74
|
format: { type: 'duration', unit: 'minute', display: 'digital' },
|
|
66
75
|
},
|
|
67
|
-
{
|
|
76
|
+
{
|
|
77
|
+
key: 'notes',
|
|
78
|
+
label: 'Notes',
|
|
79
|
+
dataPath: 'notes',
|
|
80
|
+
visibility: { minDataDepth: 'detailed' },
|
|
81
|
+
},
|
|
68
82
|
],
|
|
83
|
+
collection: {
|
|
84
|
+
viewModes: {
|
|
85
|
+
defaultMode: 'table',
|
|
86
|
+
allowedModes: ['list', 'grid', 'table'],
|
|
87
|
+
},
|
|
88
|
+
toolbar: {
|
|
89
|
+
search: true,
|
|
90
|
+
viewMode: true,
|
|
91
|
+
filters: true,
|
|
92
|
+
density: true,
|
|
93
|
+
dataDepth: true,
|
|
94
|
+
},
|
|
95
|
+
pagination: {
|
|
96
|
+
pageSize: 25,
|
|
97
|
+
pageSizeOptions: [10, 25, 50],
|
|
98
|
+
},
|
|
99
|
+
density: 'comfortable',
|
|
100
|
+
dataDepth: 'standard',
|
|
101
|
+
personalization: {
|
|
102
|
+
enabled: true,
|
|
103
|
+
persist: {
|
|
104
|
+
viewMode: true,
|
|
105
|
+
density: true,
|
|
106
|
+
dataDepth: true,
|
|
107
|
+
pageSize: true,
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
69
111
|
filters: [
|
|
70
112
|
{ key: 'status', label: 'Status', field: 'status', type: 'enum' },
|
|
71
113
|
{
|
|
@@ -88,20 +130,20 @@ export const TransactionHistory = defineDataView({
|
|
|
88
130
|
|
|
89
131
|
export function DataViewTutorialPage() {
|
|
90
132
|
return (
|
|
91
|
-
<
|
|
92
|
-
<
|
|
93
|
-
<
|
|
94
|
-
<
|
|
133
|
+
<VStack className="space-y-8">
|
|
134
|
+
<VStack className="space-y-4">
|
|
135
|
+
<H1 className="font-bold text-4xl">Display Data with DataViews</H1>
|
|
136
|
+
<P className="text-lg text-muted-foreground">
|
|
95
137
|
Define a filterable, sortable transaction history view that works
|
|
96
138
|
across web and mobile without duplicating UI code.
|
|
97
|
-
</
|
|
98
|
-
</
|
|
139
|
+
</P>
|
|
140
|
+
</VStack>
|
|
99
141
|
|
|
100
|
-
<
|
|
101
|
-
<
|
|
102
|
-
<
|
|
142
|
+
<VStack className="space-y-4">
|
|
143
|
+
<H2 className="font-bold text-2xl">1. Define the underlying query</H2>
|
|
144
|
+
<P className="text-muted-foreground">
|
|
103
145
|
First, create a query operation to fetch the data:
|
|
104
|
-
</
|
|
146
|
+
</P>
|
|
105
147
|
<CodeBlock
|
|
106
148
|
language="typescript"
|
|
107
149
|
filename="lib/specs/billing/list-transactions.ts"
|
|
@@ -120,35 +162,35 @@ export const ListTransactions = defineQuery({
|
|
|
120
162
|
policy: { auth: 'user' },
|
|
121
163
|
});`}
|
|
122
164
|
/>
|
|
123
|
-
</
|
|
165
|
+
</VStack>
|
|
124
166
|
|
|
125
|
-
<
|
|
126
|
-
<
|
|
127
|
-
<
|
|
167
|
+
<VStack className="space-y-4">
|
|
168
|
+
<H2 className="font-bold text-2xl">2. Define the DataView spec</H2>
|
|
169
|
+
<P className="text-muted-foreground">
|
|
128
170
|
Wrap your query with presentation metadata:
|
|
129
|
-
</
|
|
171
|
+
</P>
|
|
130
172
|
<CodeBlock
|
|
131
173
|
language="typescript"
|
|
132
174
|
filename="lib/specs/billing/transaction-history.data-view.ts"
|
|
133
175
|
code={DATAVIEW_TUTORIAL_EXAMPLE}
|
|
134
176
|
/>
|
|
135
|
-
<
|
|
177
|
+
<P className="text-muted-foreground text-sm">
|
|
136
178
|
The live version of this pattern is available in the canonical{' '}
|
|
137
179
|
<Link
|
|
138
180
|
href="/docs/examples/data-grid-showcase"
|
|
139
181
|
className="text-[color:var(--rust)] underline underline-offset-4"
|
|
140
182
|
>
|
|
141
|
-
Data Grid Showcase
|
|
183
|
+
<Text>Data Grid Showcase</Text>
|
|
142
184
|
</Link>
|
|
143
185
|
.
|
|
144
|
-
</
|
|
145
|
-
</
|
|
186
|
+
</P>
|
|
187
|
+
</VStack>
|
|
146
188
|
|
|
147
|
-
<
|
|
148
|
-
<
|
|
149
|
-
<
|
|
189
|
+
<VStack className="space-y-4">
|
|
190
|
+
<H2 className="font-bold text-2xl">3. Render on the frontend</H2>
|
|
191
|
+
<P className="text-muted-foreground">
|
|
150
192
|
Use the runtime renderer in your React or React Native app:
|
|
151
|
-
</
|
|
193
|
+
</P>
|
|
152
194
|
<CodeBlock
|
|
153
195
|
language="tsx"
|
|
154
196
|
filename="app/dashboard/transactions/page.tsx"
|
|
@@ -169,8 +211,11 @@ export function TransactionsPage() {
|
|
|
169
211
|
<h1 className="text-3xl font-bold mb-6">Payment History</h1>
|
|
170
212
|
<DataViewRenderer
|
|
171
213
|
spec={TransactionHistory}
|
|
172
|
-
|
|
214
|
+
items={data?.items ?? []}
|
|
173
215
|
loading={isLoading}
|
|
216
|
+
defaultViewMode="table"
|
|
217
|
+
defaultDensity="comfortable"
|
|
218
|
+
defaultDataDepth="standard"
|
|
174
219
|
onFilterChange={(filters) => {
|
|
175
220
|
// refetch with new filters
|
|
176
221
|
}}
|
|
@@ -179,34 +224,120 @@ export function TransactionsPage() {
|
|
|
179
224
|
);
|
|
180
225
|
}`}
|
|
181
226
|
/>
|
|
182
|
-
</
|
|
183
|
-
|
|
184
|
-
<
|
|
185
|
-
<
|
|
186
|
-
<
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
227
|
+
</VStack>
|
|
228
|
+
|
|
229
|
+
<VStack className="space-y-4">
|
|
230
|
+
<H2 className="font-bold text-2xl">4. Add personalization</H2>
|
|
231
|
+
<P className="text-muted-foreground">
|
|
232
|
+
When the app has a user profile or behavior insights, resolve DataView
|
|
233
|
+
preferences before rendering. The renderer receives plain props;
|
|
234
|
+
personalization stays in the app/runtime boundary.
|
|
235
|
+
</P>
|
|
236
|
+
<CodeBlock
|
|
237
|
+
language="tsx"
|
|
238
|
+
filename="app/dashboard/transactions/PersonalizedTransactions.tsx"
|
|
239
|
+
code={`'use client';
|
|
240
|
+
|
|
241
|
+
import { DataViewRenderer } from '@contractspec/lib.design-system';
|
|
242
|
+
import { resolveDataViewPreferences } from '@contractspec/lib.personalization/data-view-preferences';
|
|
243
|
+
import { createBehaviorTracker } from '@contractspec/lib.personalization';
|
|
244
|
+
|
|
245
|
+
const tracker = createBehaviorTracker({
|
|
246
|
+
store,
|
|
247
|
+
context: { tenantId: tenant.id, userId: user.id },
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const resolved = resolveDataViewPreferences({
|
|
251
|
+
spec: TransactionHistory,
|
|
252
|
+
preferences: profile.canonical,
|
|
253
|
+
insights,
|
|
254
|
+
record: savedTransactionViewPreference,
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
<DataViewRenderer
|
|
258
|
+
spec={TransactionHistory}
|
|
259
|
+
items={transactions}
|
|
260
|
+
defaultViewMode={resolved.viewMode}
|
|
261
|
+
defaultDensity={resolved.density}
|
|
262
|
+
defaultDataDepth={resolved.dataDepth}
|
|
263
|
+
pagination={{ page, pageSize: resolved.pageSize ?? 25, total }}
|
|
264
|
+
onViewModeChange={(viewMode) =>
|
|
265
|
+
tracker.trackDataViewInteraction({
|
|
266
|
+
dataViewKey: TransactionHistory.meta.key,
|
|
267
|
+
action: 'view_mode_changed',
|
|
268
|
+
viewMode,
|
|
269
|
+
})
|
|
270
|
+
}
|
|
271
|
+
onDataDepthChange={(dataDepth) =>
|
|
272
|
+
tracker.trackDataViewInteraction({
|
|
273
|
+
dataViewKey: TransactionHistory.meta.key,
|
|
274
|
+
action: 'data_depth_changed',
|
|
275
|
+
dataDepth,
|
|
276
|
+
})
|
|
277
|
+
}
|
|
278
|
+
/>;`}
|
|
279
|
+
/>
|
|
280
|
+
</VStack>
|
|
281
|
+
|
|
282
|
+
<VStack className="card-subtle space-y-4 p-6">
|
|
283
|
+
<H3 className="font-bold">Why DataViews?</H3>
|
|
284
|
+
<List className="space-y-2 text-muted-foreground text-sm">
|
|
285
|
+
<ListItem>
|
|
286
|
+
<Text>
|
|
287
|
+
Same spec renders on web (React) and mobile (React Native)
|
|
288
|
+
</Text>
|
|
289
|
+
</ListItem>
|
|
290
|
+
<ListItem>
|
|
291
|
+
<Text>Filters, sorting, and pagination handled automatically</Text>
|
|
292
|
+
</ListItem>
|
|
293
|
+
<ListItem>
|
|
294
|
+
<Text>
|
|
295
|
+
Column visibility, pinning, resizing, and row expansion stay
|
|
296
|
+
contract-driven
|
|
297
|
+
</Text>
|
|
298
|
+
</ListItem>
|
|
299
|
+
<ListItem>
|
|
300
|
+
<Text>
|
|
301
|
+
List, grid, and table modes can share one collection config with
|
|
302
|
+
toolbar and pagination defaults
|
|
303
|
+
</Text>
|
|
304
|
+
</ListItem>
|
|
305
|
+
<ListItem>
|
|
306
|
+
<Text>
|
|
307
|
+
Data depth lets summary screens hide detailed fields without
|
|
308
|
+
forking the spec
|
|
309
|
+
</Text>
|
|
310
|
+
</ListItem>
|
|
311
|
+
<ListItem>
|
|
312
|
+
<Text>
|
|
313
|
+
Typed format rules for numbers, percent values, currency, dates,
|
|
314
|
+
times, datetimes, and durations applied consistently
|
|
315
|
+
</Text>
|
|
316
|
+
</ListItem>
|
|
317
|
+
<ListItem>
|
|
318
|
+
<Text>
|
|
319
|
+
Personalization helpers can seed preferred view mode, density,
|
|
320
|
+
data depth, and page size from user preferences or behavior
|
|
321
|
+
insights
|
|
322
|
+
</Text>
|
|
323
|
+
</ListItem>
|
|
324
|
+
<ListItem>
|
|
325
|
+
<Text>Export to CSV/PDF using the same spec</Text>
|
|
326
|
+
</ListItem>
|
|
327
|
+
<ListItem>
|
|
328
|
+
<Text>A/B test different layouts without touching the backend</Text>
|
|
329
|
+
</ListItem>
|
|
330
|
+
</List>
|
|
331
|
+
</VStack>
|
|
332
|
+
|
|
333
|
+
<HStack className="items-center gap-4 pt-4">
|
|
203
334
|
<Link href="/docs/libraries/data-views" className="btn-primary">
|
|
204
|
-
DataView API Reference <ChevronRight size={16} />
|
|
335
|
+
<Text>DataView API Reference</Text> <ChevronRight size={16} />
|
|
205
336
|
</Link>
|
|
206
337
|
<Link href="/docs/specs/workflows" className="btn-ghost">
|
|
207
|
-
Next: Workflows
|
|
338
|
+
<Text>Next: Workflows</Text>
|
|
208
339
|
</Link>
|
|
209
|
-
</
|
|
210
|
-
</
|
|
340
|
+
</HStack>
|
|
341
|
+
</VStack>
|
|
211
342
|
);
|
|
212
343
|
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
export const accountImportTemplateCode = `import {
|
|
2
|
+
createImportPlan,
|
|
3
|
+
createRecordBatch,
|
|
4
|
+
defineDataExchangeTemplate,
|
|
5
|
+
previewImport,
|
|
6
|
+
} from "@contractspec/lib.data-exchange-core";
|
|
7
|
+
import { defineSchemaModel, ScalarTypeEnum } from "@contractspec/lib.schema";
|
|
8
|
+
|
|
9
|
+
const AccountImportSchema = defineSchemaModel({
|
|
10
|
+
name: "AccountImport",
|
|
11
|
+
fields: {
|
|
12
|
+
id: { type: ScalarTypeEnum.ID(), isOptional: false },
|
|
13
|
+
status: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
14
|
+
amount: { type: ScalarTypeEnum.Float_unsecure(), isOptional: false },
|
|
15
|
+
active: { type: ScalarTypeEnum.Boolean(), isOptional: false },
|
|
16
|
+
tags: { type: ScalarTypeEnum.JSON(), isOptional: true },
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const accountImportTemplate = defineDataExchangeTemplate({
|
|
21
|
+
key: "accounts.import",
|
|
22
|
+
version: "1.0.0",
|
|
23
|
+
title: "Account import",
|
|
24
|
+
columns: [
|
|
25
|
+
{
|
|
26
|
+
key: "id",
|
|
27
|
+
label: "Account ID",
|
|
28
|
+
targetField: "id",
|
|
29
|
+
required: true,
|
|
30
|
+
sourceAliases: ["Account Identifier", "External ID", "No compte"],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
key: "status",
|
|
34
|
+
label: "Status",
|
|
35
|
+
targetField: "status",
|
|
36
|
+
required: true,
|
|
37
|
+
sourceAliases: ["Statut", "State"],
|
|
38
|
+
format: { kind: "text", trim: true, case: "lowercase" },
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
key: "amount",
|
|
42
|
+
label: "Amount",
|
|
43
|
+
targetField: "amount",
|
|
44
|
+
required: true,
|
|
45
|
+
sourceAliases: ["Montant", "Balance"],
|
|
46
|
+
format: { kind: "number", decimalSeparator: ",", thousandsSeparator: "." },
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
key: "active",
|
|
50
|
+
label: "Active",
|
|
51
|
+
targetField: "active",
|
|
52
|
+
sourceAliases: ["Actif", "Enabled"],
|
|
53
|
+
format: { kind: "boolean", trueValues: ["yes", "oui"], falseValues: ["no", "non"] },
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
key: "tags",
|
|
57
|
+
label: "Tags",
|
|
58
|
+
targetField: "tags",
|
|
59
|
+
format: { kind: "split", delimiter: ";" },
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const sourceBatch = createRecordBatch([
|
|
65
|
+
{
|
|
66
|
+
"No compte": "acc-1",
|
|
67
|
+
Statut: " Active ",
|
|
68
|
+
Montant: "1.234,50",
|
|
69
|
+
Actif: "oui",
|
|
70
|
+
Tags: "vip; beta",
|
|
71
|
+
},
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
const preview = previewImport(
|
|
75
|
+
createImportPlan({
|
|
76
|
+
source: { kind: "memory", batch: sourceBatch, format: "csv" },
|
|
77
|
+
target: { kind: "memory", format: "json" },
|
|
78
|
+
schema: AccountImportSchema,
|
|
79
|
+
sourceBatch,
|
|
80
|
+
template: accountImportTemplate,
|
|
81
|
+
}),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
console.log(preview.plan.mappingSource); // "template"
|
|
85
|
+
console.log(preview.normalizedRecords[0]);`;
|
|
86
|
+
|
|
87
|
+
export const serverDryRunCode = `import { defineDataExchangeTemplate } from "@contractspec/lib.data-exchange-core";
|
|
88
|
+
import { dryRunImport, executeImport } from "@contractspec/lib.data-exchange-server";
|
|
89
|
+
|
|
90
|
+
const template = defineDataExchangeTemplate({
|
|
91
|
+
key: "accounts.import",
|
|
92
|
+
version: "1.0.0",
|
|
93
|
+
columns: [
|
|
94
|
+
{ key: "id", label: "Account ID", targetField: "id", required: true, sourceAliases: ["Account Identifier"] },
|
|
95
|
+
{ key: "amount", label: "Amount", targetField: "amount", format: { kind: "currency", currencySymbol: "€", decimalSeparator: "," } },
|
|
96
|
+
],
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const partnerSource = {
|
|
100
|
+
kind: "file",
|
|
101
|
+
path: "partner-accounts.csv",
|
|
102
|
+
format: "csv",
|
|
103
|
+
codecOptions: { csv: { delimiter: ";", skipRows: 1 } },
|
|
104
|
+
} as const;
|
|
105
|
+
|
|
106
|
+
const formatProfile = {
|
|
107
|
+
columns: {
|
|
108
|
+
amount: { kind: "currency", currencySymbol: "€", decimalSeparator: "," },
|
|
109
|
+
},
|
|
110
|
+
} as const;
|
|
111
|
+
|
|
112
|
+
const preview = await dryRunImport({
|
|
113
|
+
source: partnerSource,
|
|
114
|
+
target: { kind: "memory", format: "json" },
|
|
115
|
+
schema: AccountImportSchema,
|
|
116
|
+
template,
|
|
117
|
+
formatProfile,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const blockingIssues = preview.issues.filter((issue) => issue.severity === "error");
|
|
121
|
+
|
|
122
|
+
if (blockingIssues.length === 0) {
|
|
123
|
+
await executeImport({
|
|
124
|
+
source: partnerSource,
|
|
125
|
+
target: { kind: "memory", format: "json" },
|
|
126
|
+
schema: AccountImportSchema,
|
|
127
|
+
template,
|
|
128
|
+
formatProfile,
|
|
129
|
+
});
|
|
130
|
+
}`;
|
|
131
|
+
|
|
132
|
+
export const clientReviewCode = `"use client";
|
|
133
|
+
|
|
134
|
+
import { useDataExchangeController } from "@contractspec/lib.data-exchange-client";
|
|
135
|
+
|
|
136
|
+
export function ImportMappingReview({ preview }) {
|
|
137
|
+
const controller = useDataExchangeController({ preview });
|
|
138
|
+
const replacementSourceColumn = controller.model.ignoredSourceColumns[0];
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<section>
|
|
142
|
+
{controller.model.templateRows.map((row) => (
|
|
143
|
+
<button
|
|
144
|
+
key={row.id}
|
|
145
|
+
type="button"
|
|
146
|
+
disabled={!replacementSourceColumn}
|
|
147
|
+
onClick={() => {
|
|
148
|
+
if (!replacementSourceColumn) return;
|
|
149
|
+
controller.selectAlias(row.targetField, replacementSourceColumn);
|
|
150
|
+
}}
|
|
151
|
+
>
|
|
152
|
+
{row.label}: {row.sourceField || "Unmatched"} -> {row.targetField}
|
|
153
|
+
{row.required ? " required" : ""}
|
|
154
|
+
{row.formatLabel ? \` (\${row.formatLabel})\` : ""}
|
|
155
|
+
</button>
|
|
156
|
+
))}
|
|
157
|
+
{controller.model.unmatchedRequiredRows.length > 0 ? (
|
|
158
|
+
<p>Resolve required columns before import.</p>
|
|
159
|
+
) : null}
|
|
160
|
+
</section>
|
|
161
|
+
);
|
|
162
|
+
}`;
|
|
163
|
+
|
|
164
|
+
export const developerPrompt = `You are adding an import flow to a ContractSpec app.
|
|
165
|
+
|
|
166
|
+
Define a reusable data-exchange template for this canonical schema:
|
|
167
|
+
- target fields, required flags, and display labels
|
|
168
|
+
- known partner column aliases
|
|
169
|
+
- value formats for numbers, dates, booleans, JSON, split/join lists, currency, and percentages
|
|
170
|
+
|
|
171
|
+
Wire the template into core preview planning and server dry-run execution. Keep explicit mappings higher precedence than template resolution. Return the template, preview call, server dry-run call, and tests for alias matching plus localized formatting.`;
|
|
172
|
+
|
|
173
|
+
export const partnerPrompt = `A partner sent a CSV/JSON/XML file that does not match our recommended import template.
|
|
174
|
+
|
|
175
|
+
Compare the incoming headers and value samples against this ContractSpec data-exchange template. Propose:
|
|
176
|
+
- source-to-target column matches with confidence
|
|
177
|
+
- missing required target fields
|
|
178
|
+
- ignored source columns
|
|
179
|
+
- format overrides for localized numbers, booleans, dates, JSON, split/join lists, currency, or percentages
|
|
180
|
+
|
|
181
|
+
Do not execute the import. Produce a dry-run plan and the user-facing review copy.`;
|
|
182
|
+
|
|
183
|
+
export const verificationCode = `cd packages/libs/data-exchange-core && bun test && bun run typecheck && bun run lint:check
|
|
184
|
+
cd packages/libs/data-exchange-client && bun test && bun run typecheck && bun run lint:check
|
|
185
|
+
cd packages/libs/data-exchange-server && bun test && bun run typecheck && bun run lint:check`;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { CodeBlock } from '@contractspec/lib.design-system';
|
|
2
|
+
import { HStack, VStack } from '@contractspec/lib.design-system/layout';
|
|
3
|
+
import { List, ListItem } from '@contractspec/lib.design-system/list';
|
|
4
|
+
import {
|
|
5
|
+
Code,
|
|
6
|
+
H1,
|
|
7
|
+
H2,
|
|
8
|
+
H3,
|
|
9
|
+
P,
|
|
10
|
+
Text,
|
|
11
|
+
} from '@contractspec/lib.design-system/typography';
|
|
12
|
+
import Link from '@contractspec/lib.ui-link';
|
|
13
|
+
import { ChevronRight } from 'lucide-react';
|
|
14
|
+
import {
|
|
15
|
+
accountImportTemplateCode,
|
|
16
|
+
clientReviewCode,
|
|
17
|
+
developerPrompt,
|
|
18
|
+
partnerPrompt,
|
|
19
|
+
serverDryRunCode,
|
|
20
|
+
verificationCode,
|
|
21
|
+
} from './GuideDataExchangeImportTemplatesPage.content';
|
|
22
|
+
|
|
23
|
+
const mappingRules = [
|
|
24
|
+
'Explicit mappings win first, so existing integrations can keep their current mapping arrays.',
|
|
25
|
+
'Template resolution checks exact headers, aliases, normalized labels, and SchemaModel fallback inference.',
|
|
26
|
+
'Format profiles can override formats by target field or template column key without changing the template.',
|
|
27
|
+
'Required template columns become visible preview issues when no source column can be matched.',
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const formatKinds = [
|
|
31
|
+
'text trim and case normalization',
|
|
32
|
+
'localized numbers with decimal and thousands separators',
|
|
33
|
+
'custom true/false boolean labels',
|
|
34
|
+
'dates and datetimes with accepted input formats',
|
|
35
|
+
'JSON parsing, empty-as-null, and default values',
|
|
36
|
+
'split/join delimiters, currency symbols, and percentages',
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
export function GuideDataExchangeImportTemplatesPage() {
|
|
40
|
+
return (
|
|
41
|
+
<VStack className="space-y-8">
|
|
42
|
+
<VStack className="space-y-3">
|
|
43
|
+
<Text className="editorial-kicker">Build</Text>
|
|
44
|
+
<H1 className="font-bold text-4xl">
|
|
45
|
+
Import flexible files with data-exchange templates
|
|
46
|
+
</H1>
|
|
47
|
+
<P className="max-w-3xl text-lg text-muted-foreground leading-8">
|
|
48
|
+
Publish one recommended import shape, then let users import
|
|
49
|
+
CSV/JSON/XML files with partner-specific headers, skipped rows,
|
|
50
|
+
localized values, and alternate metadata layouts.
|
|
51
|
+
</P>
|
|
52
|
+
</VStack>
|
|
53
|
+
|
|
54
|
+
<VStack className="card-subtle space-y-4 p-6">
|
|
55
|
+
<H2 className="font-bold text-2xl">What you'll build</H2>
|
|
56
|
+
<List className="space-y-2 text-muted-foreground text-sm">
|
|
57
|
+
<ListItem>
|
|
58
|
+
<Text>
|
|
59
|
+
A canonical template with target fields, aliases, and formats.
|
|
60
|
+
</Text>
|
|
61
|
+
</ListItem>
|
|
62
|
+
<ListItem>
|
|
63
|
+
<Text>
|
|
64
|
+
A dry-run import that reports confidence, gaps, and ignored
|
|
65
|
+
columns.
|
|
66
|
+
</Text>
|
|
67
|
+
</ListItem>
|
|
68
|
+
<ListItem>
|
|
69
|
+
<Text>
|
|
70
|
+
A client review state where users remap, update formats, or accept
|
|
71
|
+
inferred mappings.
|
|
72
|
+
</Text>
|
|
73
|
+
</ListItem>
|
|
74
|
+
</List>
|
|
75
|
+
</VStack>
|
|
76
|
+
|
|
77
|
+
<VStack className="space-y-4">
|
|
78
|
+
<H2 className="font-bold text-2xl">1) Define the template</H2>
|
|
79
|
+
<P className="text-muted-foreground text-sm leading-7">
|
|
80
|
+
Use <Code>defineDataExchangeTemplate</Code> for neutral import/export
|
|
81
|
+
naming. <Code>defineImportTemplate</Code> and{' '}
|
|
82
|
+
<Code>defineExportTemplate</Code> remain available aliases.
|
|
83
|
+
</P>
|
|
84
|
+
<CodeBlock
|
|
85
|
+
language="typescript"
|
|
86
|
+
filename="src/data-exchange/accounts-import.ts"
|
|
87
|
+
code={accountImportTemplateCode}
|
|
88
|
+
/>
|
|
89
|
+
</VStack>
|
|
90
|
+
|
|
91
|
+
<VStack className="grid gap-4 md:grid-cols-2">
|
|
92
|
+
<VStack className="card-subtle space-y-3 p-6">
|
|
93
|
+
<H3 className="font-bold text-xl">Mapping precedence</H3>
|
|
94
|
+
<List className="space-y-2 text-muted-foreground text-sm">
|
|
95
|
+
{mappingRules.map((rule) => (
|
|
96
|
+
<ListItem key={rule}>
|
|
97
|
+
<Text>{rule}</Text>
|
|
98
|
+
</ListItem>
|
|
99
|
+
))}
|
|
100
|
+
</List>
|
|
101
|
+
</VStack>
|
|
102
|
+
<VStack className="card-subtle space-y-3 p-6">
|
|
103
|
+
<H3 className="font-bold text-xl">Supported value formats</H3>
|
|
104
|
+
<List className="space-y-2 text-muted-foreground text-sm">
|
|
105
|
+
{formatKinds.map((format) => (
|
|
106
|
+
<ListItem key={format}>
|
|
107
|
+
<Text>{format}</Text>
|
|
108
|
+
</ListItem>
|
|
109
|
+
))}
|
|
110
|
+
</List>
|
|
111
|
+
</VStack>
|
|
112
|
+
</VStack>
|
|
113
|
+
|
|
114
|
+
<VStack className="space-y-4">
|
|
115
|
+
<H2 className="font-bold text-2xl">
|
|
116
|
+
2) Dry-run partner CSV, JSON, or XML files on the server
|
|
117
|
+
</H2>
|
|
118
|
+
<P className="text-muted-foreground text-sm leading-7">
|
|
119
|
+
File, HTTP, and storage adapters accept codec options. CSV can set
|
|
120
|
+
delimiters, quotes, skipped rows, header rows, or explicit columns.
|
|
121
|
+
JSON can read records and metadata from custom keys. XML can use
|
|
122
|
+
custom root, record, metadata, and attribute fields.
|
|
123
|
+
</P>
|
|
124
|
+
<CodeBlock
|
|
125
|
+
language="typescript"
|
|
126
|
+
filename="src/server/import-accounts.ts"
|
|
127
|
+
code={serverDryRunCode}
|
|
128
|
+
/>
|
|
129
|
+
</VStack>
|
|
130
|
+
|
|
131
|
+
<VStack className="space-y-4">
|
|
132
|
+
<H2 className="font-bold text-2xl">3) Let users review the mapping</H2>
|
|
133
|
+
<P className="text-muted-foreground text-sm leading-7">
|
|
134
|
+
The shared controller exposes template rows, matched source columns,
|
|
135
|
+
confidence, required status, formatting summaries, unmatched required
|
|
136
|
+
rows, and ignored source columns. Actions let users remap columns,
|
|
137
|
+
select aliases, update field formats, reset to the template, or accept
|
|
138
|
+
inferred mappings.
|
|
139
|
+
</P>
|
|
140
|
+
<CodeBlock
|
|
141
|
+
language="tsx"
|
|
142
|
+
filename="src/components/ImportMappingReview.tsx"
|
|
143
|
+
code={clientReviewCode}
|
|
144
|
+
/>
|
|
145
|
+
</VStack>
|
|
146
|
+
|
|
147
|
+
<VStack className="space-y-4">
|
|
148
|
+
<H2 className="font-bold text-2xl">4) Verify the package stack</H2>
|
|
149
|
+
<CodeBlock
|
|
150
|
+
language="bash"
|
|
151
|
+
filename="verification"
|
|
152
|
+
code={verificationCode}
|
|
153
|
+
/>
|
|
154
|
+
</VStack>
|
|
155
|
+
|
|
156
|
+
<VStack className="grid gap-4 md:grid-cols-2">
|
|
157
|
+
<VStack className="card-subtle space-y-3 p-6">
|
|
158
|
+
<H3 className="font-bold text-xl">Prompt: build a template</H3>
|
|
159
|
+
<CodeBlock
|
|
160
|
+
language="markdown"
|
|
161
|
+
filename="template-authoring.prompt.md"
|
|
162
|
+
code={developerPrompt}
|
|
163
|
+
/>
|
|
164
|
+
</VStack>
|
|
165
|
+
<VStack className="card-subtle space-y-3 p-6">
|
|
166
|
+
<H3 className="font-bold text-xl">Prompt: inspect a partner file</H3>
|
|
167
|
+
<CodeBlock
|
|
168
|
+
language="markdown"
|
|
169
|
+
filename="partner-import-review.prompt.md"
|
|
170
|
+
code={partnerPrompt}
|
|
171
|
+
/>
|
|
172
|
+
</VStack>
|
|
173
|
+
</VStack>
|
|
174
|
+
|
|
175
|
+
<HStack className="flex flex-wrap items-center gap-4 pt-4">
|
|
176
|
+
<Link href="/llms/lib.data-exchange-core" className="btn-primary">
|
|
177
|
+
<Text>Core package guide</Text>
|
|
178
|
+
<ChevronRight size={16} />
|
|
179
|
+
</Link>
|
|
180
|
+
<Link href="/llms/lib.data-exchange-server" className="btn-ghost">
|
|
181
|
+
<Text>Server package guide</Text>
|
|
182
|
+
</Link>
|
|
183
|
+
</HStack>
|
|
184
|
+
</VStack>
|
|
185
|
+
);
|
|
186
|
+
}
|