@expresscsv/sdk 0.1.25 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -41
- package/dist/index.d.cts +255 -119
- package/dist/index.d.mts +255 -119
- package/dist/index.d.ts +255 -119
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,21 +36,20 @@ const schema = x.row({
|
|
|
36
36
|
const importer = new CSVImporter({
|
|
37
37
|
schema,
|
|
38
38
|
getSessionToken: async () => fetchSessionToken(),
|
|
39
|
-
|
|
39
|
+
importNamespace: "user-import",
|
|
40
40
|
title: "Import Users",
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
// Open the importer and process data in chunks
|
|
44
44
|
importer.open({
|
|
45
45
|
onData: (chunk, next) => {
|
|
46
|
-
console.log(`Chunk ${chunk.
|
|
46
|
+
console.log(`Chunk ${chunk.chunkIndex + 1}/${chunk.totalChunks}`);
|
|
47
47
|
console.log("Session:", chunk.sessionId);
|
|
48
|
-
console.log("Idempotency key:", chunk.chunkIdempotencyKey);
|
|
49
48
|
console.log("Records:", chunk.records);
|
|
50
49
|
// Process your validated, typed records here
|
|
51
50
|
next();
|
|
52
51
|
},
|
|
53
|
-
onComplete: ({ sessionId }) => {
|
|
52
|
+
onComplete: ({ sessionId, deliveryId }) => {
|
|
54
53
|
console.log("All chunks processed successfully for", sessionId);
|
|
55
54
|
},
|
|
56
55
|
onError: (error, { sessionId }) => {
|
|
@@ -69,18 +68,19 @@ ExpressCSV delivers imported data through `onData`. Your app receives validated
|
|
|
69
68
|
|
|
70
69
|
```typescript
|
|
71
70
|
importer.open({
|
|
72
|
-
chunkSize: 500,
|
|
71
|
+
chunkSize: { unit: "kb", value: 500 },
|
|
73
72
|
onData: async (chunk, next) => {
|
|
74
|
-
const response = await fetch("/api/import-users/chunks", {
|
|
73
|
+
const response = await fetch("/your-api/import-users/chunks", {
|
|
75
74
|
method: "POST",
|
|
76
75
|
headers: {
|
|
77
76
|
"Content-Type": "application/json",
|
|
77
|
+
Authorization: `Bearer ${accessToken}`,
|
|
78
78
|
},
|
|
79
79
|
body: JSON.stringify({
|
|
80
80
|
sessionId: chunk.sessionId,
|
|
81
|
-
|
|
81
|
+
deliveryId: chunk.deliveryId,
|
|
82
|
+
chunkIndex: chunk.chunkIndex,
|
|
82
83
|
records: chunk.records,
|
|
83
|
-
currentChunkIndex: chunk.currentChunkIndex,
|
|
84
84
|
totalChunks: chunk.totalChunks,
|
|
85
85
|
totalRecords: chunk.totalRecords,
|
|
86
86
|
}),
|
|
@@ -92,8 +92,15 @@ importer.open({
|
|
|
92
92
|
|
|
93
93
|
next();
|
|
94
94
|
},
|
|
95
|
-
onComplete: ({ sessionId }) => {
|
|
96
|
-
|
|
95
|
+
onComplete: async ({ sessionId, deliveryId }) => {
|
|
96
|
+
await fetch("/your-api/import-users/complete", {
|
|
97
|
+
method: "POST",
|
|
98
|
+
headers: {
|
|
99
|
+
"Content-Type": "application/json",
|
|
100
|
+
Authorization: `Bearer ${accessToken}`,
|
|
101
|
+
},
|
|
102
|
+
body: JSON.stringify({ sessionId, deliveryId }),
|
|
103
|
+
});
|
|
97
104
|
},
|
|
98
105
|
});
|
|
99
106
|
```
|
|
@@ -102,12 +109,13 @@ Each delivered chunk includes:
|
|
|
102
109
|
|
|
103
110
|
- `records`
|
|
104
111
|
- `sessionId`
|
|
105
|
-
- `
|
|
106
|
-
- `
|
|
112
|
+
- `deliveryId`
|
|
113
|
+
- `chunkIndex`
|
|
107
114
|
- `totalChunks`
|
|
108
115
|
- `totalRecords`
|
|
109
116
|
|
|
110
|
-
|
|
117
|
+
Each time the user finishes the import, ExpressCSV creates a new `deliveryId` for that `sessionId`. Your `onData` code can fail, and the user can retry delivery without starting the import over. Store chunks by `(sessionId, deliveryId, chunkIndex)`, then finalize the `deliveryId` from `onComplete` after all of its chunks are accepted.
|
|
118
|
+
|
|
111
119
|
|
|
112
120
|
## Preloading
|
|
113
121
|
|
|
@@ -118,7 +126,7 @@ By default, the SDK preloads the importer in a hidden iframe for instant display
|
|
|
118
126
|
const importer = new CSVImporter({
|
|
119
127
|
schema,
|
|
120
128
|
getSessionToken: async () => fetchSessionToken(),
|
|
121
|
-
|
|
129
|
+
importNamespace: "user-import",
|
|
122
130
|
});
|
|
123
131
|
|
|
124
132
|
// Later, the importer appears instantly
|
|
@@ -131,7 +139,7 @@ To disable preloading (there will be a brief loading screen instead):
|
|
|
131
139
|
const importer = new CSVImporter({
|
|
132
140
|
schema,
|
|
133
141
|
getSessionToken: async () => fetchSessionToken(),
|
|
134
|
-
|
|
142
|
+
importNamespace: "user-import",
|
|
135
143
|
preload: false,
|
|
136
144
|
});
|
|
137
145
|
```
|
|
@@ -155,7 +163,7 @@ const candidateSchema = x.row({
|
|
|
155
163
|
const importer = new CSVImporter({
|
|
156
164
|
schema: candidateSchema,
|
|
157
165
|
getSessionToken: async () => fetchSessionToken(),
|
|
158
|
-
|
|
166
|
+
importNamespace: "candidate-import",
|
|
159
167
|
templateDownload: {
|
|
160
168
|
source: "generate",
|
|
161
169
|
formats: ["csv", "xlsx"],
|
|
@@ -179,10 +187,10 @@ Customize the importer's appearance with the `theme`, `colorMode`, `customCSS`,
|
|
|
179
187
|
Use the `theme` option to override CSS variables (colors, radius, typography). Pass either a single theme (applies to both light and dark) or a dual-mode theme with separate light/dark values:
|
|
180
188
|
|
|
181
189
|
```typescript
|
|
182
|
-
import { CSVImporter, x, type
|
|
190
|
+
import { CSVImporter, x, type Theme } from "@expresscsv/sdk";
|
|
183
191
|
|
|
184
192
|
// Single theme (both modes)
|
|
185
|
-
const theme:
|
|
193
|
+
const theme: Theme = {
|
|
186
194
|
primary: "#4F46E5",
|
|
187
195
|
"primary-foreground": "#ffffff",
|
|
188
196
|
background: "#ffffff",
|
|
@@ -193,7 +201,7 @@ const theme: ECSVTheme = {
|
|
|
193
201
|
};
|
|
194
202
|
|
|
195
203
|
// Dual-mode (light and dark)
|
|
196
|
-
const dualTheme:
|
|
204
|
+
const dualTheme: Theme = {
|
|
197
205
|
modes: {
|
|
198
206
|
light: {
|
|
199
207
|
primary: "#4F46E5",
|
|
@@ -211,7 +219,7 @@ const dualTheme: ECSVTheme = {
|
|
|
211
219
|
const importer = new CSVImporter({
|
|
212
220
|
schema,
|
|
213
221
|
getSessionToken: async () => fetchSessionToken(),
|
|
214
|
-
|
|
222
|
+
importNamespace: "user-import",
|
|
215
223
|
theme,
|
|
216
224
|
});
|
|
217
225
|
```
|
|
@@ -255,7 +263,7 @@ Control light/dark mode with `colorMode`:
|
|
|
255
263
|
const importer = new CSVImporter({
|
|
256
264
|
schema,
|
|
257
265
|
getSessionToken: async () => fetchSessionToken(),
|
|
258
|
-
|
|
266
|
+
importNamespace: "user-import",
|
|
259
267
|
colorMode: "system", // 'light' | 'dark' | 'system'
|
|
260
268
|
});
|
|
261
269
|
```
|
|
@@ -268,7 +276,7 @@ Inject custom CSS for fine-grained styling overrides.
|
|
|
268
276
|
const importer = new CSVImporter({
|
|
269
277
|
schema,
|
|
270
278
|
getSessionToken: async () => fetchSessionToken(),
|
|
271
|
-
|
|
279
|
+
importNamespace: "user-import",
|
|
272
280
|
customCSS: `
|
|
273
281
|
.ecsv [data-step="upload"] {
|
|
274
282
|
border-radius: 1rem;
|
|
@@ -288,7 +296,7 @@ Load custom fonts via the `fonts` option:
|
|
|
288
296
|
const importer = new CSVImporter({
|
|
289
297
|
schema,
|
|
290
298
|
getSessionToken: async () => fetchSessionToken(),
|
|
291
|
-
|
|
299
|
+
importNamespace: "user-import",
|
|
292
300
|
fonts: {
|
|
293
301
|
title: { source: "google", name: "Space Grotesk", weights: [400, 600, 700] },
|
|
294
302
|
body: { source: "custom", url: "https://example.com/font.woff2", format: "woff2" },
|
|
@@ -411,7 +419,6 @@ All field types support:
|
|
|
411
419
|
|---|---|
|
|
412
420
|
| `.label(text)` | User-facing label shown in the importer |
|
|
413
421
|
| `.description(text)` | Help text for the field |
|
|
414
|
-
| `.example(text)` | Example value shown as placeholder |
|
|
415
422
|
| `.optional()` | Makes the field optional (default is required) |
|
|
416
423
|
| `.refine(fn)` | Custom validation function |
|
|
417
424
|
|
|
@@ -551,26 +558,26 @@ new CSVImporter(options: SDKOptions)
|
|
|
551
558
|
|---|---|---|---|---|
|
|
552
559
|
| `schema` | Schema | Yes | - | Schema definition created with `x.row()` |
|
|
553
560
|
| `getSessionToken` | `() => Promise<string>` | Yes | - | Async callback that asks your backend for a short-lived importer session token |
|
|
554
|
-
| `
|
|
561
|
+
| `importNamespace` | `string` | Yes | - | Stable namespace string your app assigns to this importer configuration. Keep it the same for the same workflow; use a different value for different importers. |
|
|
555
562
|
| `title` | `string` | No | - | Title shown in the importer header |
|
|
556
563
|
| `preload` | `boolean` | No | `true` | Preload the importer for instant display |
|
|
557
564
|
| `debug` | `boolean` | No | `false` | Enable debug logging |
|
|
558
|
-
| `theme` | `
|
|
565
|
+
| `theme` | `Theme` | No | - | Custom theme configuration |
|
|
559
566
|
| `colorMode` | `ColorModePref` | No | - | Light/dark mode (`'light'`, `'dark'`, or `'system'`) |
|
|
560
567
|
| `customCSS` | `string` | No | - | Custom CSS to inject into the importer |
|
|
561
|
-
| `fonts` | `Record<string,
|
|
568
|
+
| `fonts` | `Record<string, FontSource>` | No | - | Custom font sources |
|
|
562
569
|
| `stepDisplay` | `'progressBar' \| 'segmented' \| 'numbered'` | No | `'progressBar'` | Step indicator style |
|
|
563
570
|
| `previewSchemaBeforeUpload` | `boolean` | No | `true` | Show schema preview before upload |
|
|
564
|
-
| `
|
|
565
|
-
| `
|
|
571
|
+
| `columnMatching` | `{ type: "managed"; exact?: boolean; caseInsensitive?: boolean; normalized?: boolean; inference?: boolean } \| { type: "custom"; columnMatchHandler: (...) => Promise<...> }` | No | `undefined` | Configure managed column matching or provide a custom matcher |
|
|
572
|
+
| `promptedEdits` | `{ type: "managed" } \| { type: "custom"; promptedEditHandler: (...) => Promise<...> }` | No | `undefined` | Enable managed prompted edits or provide a custom edit handler |
|
|
566
573
|
| `templateDownload` | `TemplateDownloadOptions<TSchema>` | No | - | Template download configuration with optional schema-typed example rows |
|
|
567
|
-
| `
|
|
574
|
+
| `sessionRecovery` | `SessionRecoveryOptions` | No | - | Enable Recovered sessions with the built-in local backend or a custom adapter implementing `get`, `set`, and `remove` |
|
|
568
575
|
| `locale` | `DeepPartial<ExpressCSVLocaleInput>` | No | - | Localization overrides |
|
|
569
576
|
| `disableStatusStep` | `boolean` | No | - | Skip the success/error status screen |
|
|
570
577
|
|
|
571
578
|
#### `open(options)`
|
|
572
579
|
|
|
573
|
-
Opens the importer
|
|
580
|
+
Opens the importer.
|
|
574
581
|
|
|
575
582
|
```typescript
|
|
576
583
|
importer.open(options: OpenOptions): void
|
|
@@ -578,14 +585,13 @@ importer.open(options: OpenOptions): void
|
|
|
578
585
|
|
|
579
586
|
| Option | Type | Required | Description |
|
|
580
587
|
|---|---|---|---|
|
|
581
|
-
| `onData` | `(chunk: RecordsChunk<T>, next: () => void) => void` | Yes | Callback for each delivered chunk of records. Call `next()`
|
|
582
|
-
| `chunkSize` | `
|
|
588
|
+
| `onData` | `(chunk: RecordsChunk<T>, next: () => void) => void` | Yes | Callback for each delivered chunk of records. Call `next()` after your backend accepts the current chunk. |
|
|
589
|
+
| `chunkSize` | `ChunkSize` | No | Delivery packet size. Defaults to `{ unit: "kb", value: 500 }`. `kb` uses decimal kilobytes (`1 KB = 1000 bytes`). Use `{ unit: "kb", value: 500 }` for KB or `{ unit: "rows", value: 500 }` for row counts. Zero or negative values send all records in one chunk. |
|
|
583
590
|
| `onComplete` | `(context: { sessionId: string }) => void` | No | Called when all chunks have been processed |
|
|
584
591
|
| `onCancel` | `(context: { sessionId: string }) => void` | No | Called when the user cancels the import |
|
|
585
592
|
| `onError` | `(error: Error, context: { sessionId: string }) => void` | No | Called when an error occurs |
|
|
586
|
-
| `
|
|
587
|
-
| `
|
|
588
|
-
| `onStepChange` | `(stepId, previousStepId?) => void` | No | Called when the wizard step changes |
|
|
593
|
+
| `onOpen` | `(context: { sessionId: string }) => void` | No | Called when the importer opens |
|
|
594
|
+
| `onStepChange` | `(stepId, previousStepId?) => void` | No | Called when the importer step changes |
|
|
589
595
|
|
|
590
596
|
#### `close(reason?)`
|
|
591
597
|
|
|
@@ -601,13 +607,13 @@ await importer.close(reason?: 'user_close' | 'cancel' | 'complete' | 'error'): P
|
|
|
601
607
|
|
|
602
608
|
| Method | Returns | Description |
|
|
603
609
|
|---|---|---|
|
|
604
|
-
| `
|
|
610
|
+
| `getStatus()` | `ImporterStatus` | Current importer lifecycle status |
|
|
605
611
|
| `getIsReady()` | `boolean` | Whether the importer is ready or open |
|
|
606
612
|
| `getIsOpen()` | `boolean` | Whether the importer is currently open |
|
|
607
613
|
| `getConnectionStatus()` | `boolean` | Whether the iframe connection is active |
|
|
608
614
|
| `getCanRestart()` | `boolean` | Whether the importer can be restarted |
|
|
609
615
|
| `getLastError()` | `Error \| null` | Last error, if any |
|
|
610
|
-
| `
|
|
616
|
+
| `getStatusSnapshot()` | `object` | Comprehensive status snapshot |
|
|
611
617
|
|
|
612
618
|
#### `restart(newOptions?)`
|
|
613
619
|
|
|
@@ -621,10 +627,10 @@ The object passed to `onData` callbacks:
|
|
|
621
627
|
interface RecordsChunk<T> {
|
|
622
628
|
records: T[]; // Automatically typed to your schema
|
|
623
629
|
totalChunks: number;
|
|
624
|
-
|
|
630
|
+
chunkIndex: number;
|
|
625
631
|
totalRecords: number;
|
|
626
632
|
sessionId: string;
|
|
627
|
-
|
|
633
|
+
deliveryId: string;
|
|
628
634
|
}
|
|
629
635
|
```
|
|
630
636
|
|
|
@@ -647,7 +653,7 @@ type Row = Infer<typeof schema>;
|
|
|
647
653
|
const importer = new CSVImporter({
|
|
648
654
|
schema,
|
|
649
655
|
getSessionToken: async () => fetchSessionToken(),
|
|
650
|
-
|
|
656
|
+
importNamespace: "user-import",
|
|
651
657
|
});
|
|
652
658
|
|
|
653
659
|
importer.open({
|