@expresscsv/react 0.1.22 → 0.1.23

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 CHANGED
@@ -48,17 +48,19 @@ function App() {
48
48
  console.log(
49
49
  `Chunk ${chunk.currentChunkIndex + 1}/${chunk.totalChunks}`
50
50
  );
51
+ console.log("Session:", chunk.sessionId);
52
+ console.log("Idempotency key:", chunk.chunkIdempotencyKey);
51
53
  console.log("Records:", chunk.records);
52
54
  next();
53
55
  },
54
- onComplete: () => {
55
- console.log("All chunks processed");
56
+ onComplete: ({ sessionId }) => {
57
+ console.log("All chunks processed for", sessionId);
56
58
  },
57
- onCancel: () => {
58
- console.log("User cancelled");
59
+ onCancel: ({ sessionId }) => {
60
+ console.log("User cancelled", sessionId);
59
61
  },
60
- onError: (error) => {
61
- console.error("Import error:", error);
62
+ onError: (error, { sessionId }) => {
63
+ console.error("Import error for", sessionId, error);
62
64
  },
63
65
  });
64
66
  };
@@ -273,200 +275,50 @@ const { open } = useExpressCSV({
273
275
  });
274
276
  ```
275
277
 
276
- ## Webhook Delivery
278
+ ## Delivery
277
279
 
278
- Deliver data to a server endpoint instead of (or in addition to) processing locally:
280
+ ExpressCSV delivers imported data through `onData`. Your app receives validated chunks in the browser and can forward them to your backend using whatever request shape fits your stack.
279
281
 
280
282
  ```tsx
281
- import { useExpressCSV, x } from "@expresscsv/react";
282
-
283
- const schema = x.row({
284
- name: x.string().label("Full Name"),
285
- email: x.string().email().label("Email Address"),
286
- });
287
-
288
- function App() {
289
- const { open } = useExpressCSV({
290
- schema,
291
- getSessionToken: async () => fetchSessionToken(),
292
- importIdentifier: "user-import",
293
- title: "Import Users",
294
- });
295
-
296
- const handleImport = () => {
297
- open({
298
- webhook: {
299
- url: "https://api.example.com/webhooks/csv-import",
300
- method: "POST",
301
- headers: {
302
- Authorization: "Bearer your-api-token",
303
- },
304
- metadata: {
305
- source: "react-app",
306
- userId: "user-123",
307
- },
308
- },
309
- onComplete: () => {
310
- console.log("Webhook delivery initiated");
311
- },
312
- onError: (error) => {
313
- console.error("Delivery error:", error);
283
+ open({
284
+ chunkSize: 500,
285
+ onData: async (chunk, next) => {
286
+ const response = await fetch("/api/import-users/chunks", {
287
+ method: "POST",
288
+ headers: {
289
+ "Content-Type": "application/json",
314
290
  },
291
+ body: JSON.stringify({
292
+ sessionId: chunk.sessionId,
293
+ chunkIdempotencyKey: chunk.chunkIdempotencyKey,
294
+ records: chunk.records,
295
+ currentChunkIndex: chunk.currentChunkIndex,
296
+ totalChunks: chunk.totalChunks,
297
+ totalRecords: chunk.totalRecords,
298
+ }),
315
299
  });
316
- };
317
-
318
- return <button onClick={handleImport}>Import CSV via Webhook</button>;
319
- }
320
- ```
321
-
322
- ### Webhook Payload Structure
323
300
 
324
- Each chunk is delivered as a JSON `POST` (or whichever method you configured) to your endpoint. The request body has this shape:
325
-
326
- ```typescript
327
- interface WebhookPayload {
328
- /** Imported records for this chunk, matching your schema */
329
- records: Record<string, unknown>[];
330
- /** 0-based index of the current chunk */
331
- chunkIndex: number;
332
- /** Total number of chunks in this delivery */
333
- totalChunks: number;
334
- /** Total number of records across all chunks */
335
- totalRecords: number;
336
- /** Present only if you passed `metadata` in `WebhookConfig` */
337
- metadata?: Record<string, unknown>;
338
- /** Delivery context added by ExpressCSV */
339
- delivery: {
340
- environmentName: string;
341
- environmentType: string;
342
- teamSlug: string;
343
- importIdentifier: string;
344
- deliveryId: string;
345
- timestamp: string; // ISO 8601
346
- };
347
- }
348
- ```
349
-
350
- When you already have a schema, prefer `InferWebhookPayload<typeof schema>` from `@expresscsv/schemas` instead of rewriting this object shape by hand.
351
-
352
- Example payload:
353
-
354
- ```json
355
- {
356
- "records": [
357
- { "name": "Alice Johnson", "email": "alice@example.com" },
358
- { "name": "Bob Smith", "email": "bob@example.com" }
359
- ],
360
- "chunkIndex": 0,
361
- "totalChunks": 3,
362
- "totalRecords": 2500,
363
- "metadata": {
364
- "source": "react-app",
365
- "userId": "user-123"
366
- },
367
- "delivery": {
368
- "environmentName": "Production",
369
- "environmentType": "production",
370
- "teamSlug": "my-team",
371
- "importIdentifier": "user-import",
372
- "deliveryId": "del_abc123",
373
- "timestamp": "2026-03-02T14:30:00.000Z"
374
- }
375
- }
376
- ```
377
-
378
- The request includes a `Content-Type: application/json` header plus any custom `headers` you specified in `WebhookConfig`.
379
-
380
- ### Handling Webhooks on Your Server
381
-
382
- Your endpoint should return a **2xx** status code to acknowledge each chunk. Non-2xx responses trigger the following retry behaviour:
383
-
384
- - **5xx** and **429** responses are retried automatically (up to 5 attempts per chunk).
385
- - **4xx** responses (except 429) are treated as permanent failures and are **not** retried.
386
-
387
- Chunks are delivered **serially** — the next chunk is only sent after the previous one succeeds.
388
-
389
- If your schema lives in shared or backend code via `@expresscsv/schemas`, you can reuse the built-in webhook payload helper in your webhook handler:
390
-
391
- ```typescript
392
- import express from "express";
393
- import type { InferWebhookPayload } from "@expresscsv/schemas";
394
- import { candidateSchema } from "../shared/candidate-schema";
395
-
396
- const app = express();
397
- app.use(express.json());
398
-
399
- app.post("/webhooks/csv-import", async (req, res) => {
400
- const payload = req.body as InferWebhookPayload<typeof candidateSchema>;
401
-
402
- await importCandidates(payload.records);
403
-
404
- res.status(200).json({ ok: true });
405
- });
406
- ```
407
-
408
- Below is a minimal Express.js example:
409
-
410
- ```typescript
411
- import express from "express";
412
-
413
- const app = express();
414
- app.use(express.json());
415
-
416
- app.post("/webhooks/csv-import", async (req, res) => {
417
- const token = req.headers.authorization;
418
- if (token !== "Bearer your-api-token") {
419
- return res.status(401).json({ error: "Unauthorized" });
420
- }
421
-
422
- const { records, chunkIndex, totalChunks, totalRecords, metadata, delivery } =
423
- req.body;
424
-
425
- try {
426
- // Process the records — e.g. insert into your database
427
- await db.insertMany("users", records);
428
-
429
- console.log(
430
- `Chunk ${chunkIndex + 1}/${totalChunks} processed ` +
431
- `(${records.length} records, delivery ${delivery.deliveryId})`
432
- );
433
-
434
- res.status(200).json({ success: true });
435
- } catch (error) {
436
- // Return 500 so ExpressCSV retries this chunk
437
- console.error("Failed to process chunk:", error);
438
- res.status(500).json({ error: "Internal server error" });
439
- }
440
- });
441
-
442
- app.listen(3000);
443
- ```
444
-
445
- **Tips:**
446
-
447
- - Use `delivery.deliveryId` and `chunkIndex` to **deduplicate** retried chunks (the same chunk may be delivered more than once on retry).
448
- - Use `chunkIndex` and `totalChunks` to track progress and know when the full import is complete (`chunkIndex === totalChunks - 1` for the last chunk).
449
- - Store `metadata` alongside imported records if you need to correlate the import with a specific user or action in your app.
450
-
451
- ### Combined Local Callback and Webhook
301
+ if (!response.ok) {
302
+ throw new Error("Backend rejected this import chunk");
303
+ }
452
304
 
453
- ```tsx
454
- open({
455
- chunkSize: 500,
456
- onData: async (chunk, next) => {
457
- await saveToLocalDatabase(chunk.records);
458
305
  next();
459
306
  },
460
- webhook: {
461
- url: "https://api.example.com/webhooks/csv-import",
462
- headers: { Authorization: "Bearer your-api-token" },
463
- },
464
- onComplete: () => {
465
- console.log("Local processing and webhook delivery complete");
307
+ onComplete: ({ sessionId }) => {
308
+ console.log("Import complete for", sessionId);
466
309
  },
467
310
  });
468
311
  ```
469
312
 
313
+ Each delivered chunk includes:
314
+
315
+ - `records`
316
+ - `sessionId`
317
+ - `chunkIdempotencyKey`
318
+ - `currentChunkIndex`
319
+ - `totalChunks`
320
+ - `totalRecords`
321
+
470
322
  ## Advanced Example
471
323
 
472
324
  ```tsx
@@ -508,13 +360,14 @@ function CandidateImporter() {
508
360
  await importCandidates(chunk.records);
509
361
  next();
510
362
  },
511
- onComplete: () => {
512
- setStatus("Import complete!");
363
+ onComplete: ({ sessionId }) => {
364
+ setStatus(`Import complete: ${sessionId}`);
513
365
  },
514
- onError: (error) => {
515
- setStatus(`Error: ${error.message}`);
366
+ onError: (error, { sessionId }) => {
367
+ setStatus(`Error in ${sessionId}: ${error.message}`);
516
368
  },
517
- onCancel: () => {
369
+ onCancel: ({ sessionId }) => {
370
+ console.log("Cancelled session", sessionId);
518
371
  setStatus(null);
519
372
  },
520
373
  });
@@ -562,37 +415,37 @@ function useExpressCSV<TSchema>(
562
415
  | `fonts` | `Record<string, ECSVFontSource>` | No | - | Custom font sources |
563
416
  | `stepDisplay` | `'progressBar' \| 'segmented' \| 'numbered'` | No | `'progressBar'` | Step indicator style |
564
417
  | `previewSchemaBeforeUpload` | `boolean` | No | `true` | Show schema preview before upload |
418
+ | `aiColumnMatching` | `boolean` | No | `true` | Enable AI-assisted column matching |
419
+ | `aiTransform` | `boolean` | No | `true` | Enable AI-assisted transform generation |
565
420
  | `templateDownload` | `TemplateDownloadOptions<TSchema>` | No | - | Template download configuration with optional schema-typed example rows |
566
- | `saveSession` | `boolean` | No | - | Persist session state |
421
+ | `storage` | `{ type: "local" } \| { type: "custom", ... }` | No | - | Enable Recovered Sessions with the built-in local backend or a custom adapter implementing `get`, `set`, and `remove` |
567
422
  | `locale` | `DeepPartial<ExpressCSVLocaleInput>` | No | - | Localization overrides |
423
+ | `disableStatusStep` | `boolean` | No | - | Skip the success/error status screen |
568
424
 
569
425
  #### Return Value
570
426
 
571
427
  | Property | Type | Description |
572
428
  |---|---|---|
573
- | `open` | `(options: OpenOptions) => void` | Opens the importer. Requires at least one of `onData` or `webhook`. |
429
+ | `open` | `(options: OpenOptions) => void` | Opens the importer. Requires `onData` for delivery. |
574
430
  | `importerState` | `ImporterState` | Current importer lifecycle state (reactive) |
575
431
  | `isInitialising` | `boolean` | `true` while the importer is initializing or opening |
576
432
  | `isOpen` | `boolean` | `true` while the importer is open |
577
433
 
578
434
  ### `OpenOptions<T>`
579
435
 
580
- Options passed to `open()`. At least one of `onData` or `webhook` must be provided.
436
+ Options passed to `open()`.
581
437
 
582
438
  | Option | Type | Required | Description |
583
439
  |---|---|---|---|
584
- | `onData` | `(chunk: RecordsChunk<T>, next: () => void) => void` | * | Callback for each chunk. Call `next()` to continue. |
585
- | `webhook` | `WebhookConfig` | * | Webhook endpoint for server-side delivery |
440
+ | `onData` | `(chunk: RecordsChunk<T>, next: () => void) => void` | Yes | Callback for each delivered chunk. Call `next()` to continue. |
586
441
  | `chunkSize` | `number` | No | Records per chunk (default: 1000) |
587
- | `onComplete` | `() => void` | No | Called when all chunks have been processed |
588
- | `onCancel` | `() => void` | No | Called when the user cancels the import |
589
- | `onError` | `(error: Error) => void` | No | Called when an error occurs |
442
+ | `onComplete` | `(context: { sessionId: string }) => void` | No | Called when all chunks have been processed |
443
+ | `onCancel` | `(context: { sessionId: string }) => void` | No | Called when the user cancels the import |
444
+ | `onError` | `(error: Error, context: { sessionId: string }) => void` | No | Called when an error occurs |
590
445
  | `onImporterOpen` | `() => void` | No | Called when the importer opens |
591
- | `onImporterClose` | `(reason: string) => void` | No | Called when the importer closes |
446
+ | `onImporterClose` | `(reason: 'user_close' \| 'cancel' \| 'complete' \| 'error') => void` | No | Called when the importer closes |
592
447
  | `onStepChange` | `(stepId, previousStepId?) => void` | No | Called when the wizard step changes |
593
448
 
594
- \* At least one of `onData` or `webhook` is required.
595
-
596
449
  ### `RecordsChunk<T>`
597
450
 
598
451
  ```typescript
@@ -601,19 +454,8 @@ interface RecordsChunk<T> {
601
454
  totalChunks: number;
602
455
  currentChunkIndex: number;
603
456
  totalRecords: number;
604
- }
605
- ```
606
-
607
- ### `WebhookConfig`
608
-
609
- ```typescript
610
- interface WebhookConfig {
611
- url: string;
612
- headers?: Record<string, string>;
613
- method?: "POST" | "PUT" | "PATCH";
614
- timeout?: number;
615
- retries?: number;
616
- metadata?: Record<string, unknown>;
457
+ sessionId: string;
458
+ chunkIdempotencyKey: string;
617
459
  }
618
460
  ```
619
461
 
@@ -621,7 +463,7 @@ interface WebhookConfig {
621
463
 
622
464
  The `x` schema builder provides a type-safe, fluent API for defining your CSV structure.
623
465
 
624
- For apps that share schemas with backend code or webhook receivers, prefer defining the schema in `@expresscsv/schemas` and importing it into your React app. That keeps schema authoring free of React and importer dependencies, while also giving your backend access to `InferWebhookPayload<typeof schema>`.
466
+ For apps that share schemas with backend code, prefer defining the schema in `@expresscsv/schemas` and importing it into your React app. That keeps schema authoring free of React and importer dependencies.
625
467
 
626
468
  #### Field Types
627
469
 
package/dist/index.d.cts CHANGED
@@ -1878,6 +1878,13 @@ declare const CurrencyCodes: readonly [{
1878
1878
  readonly numeric: "932";
1879
1879
  }];
1880
1880
 
1881
+ export declare type CustomStorageOptions = {
1882
+ type: 'custom';
1883
+ get: (key: StorageKey) => Promise<StoredSession | null>;
1884
+ set: (key: StorageKey, value: StoredSession) => Promise<void>;
1885
+ remove: (key: StorageKey) => Promise<void>;
1886
+ };
1887
+
1881
1888
  declare interface DatetimeOptions {
1882
1889
  message?: string;
1883
1890
  }
@@ -1886,19 +1893,9 @@ export declare type DeepPartial<T> = {
1886
1893
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
1887
1894
  };
1888
1895
 
1889
- /**
1890
- * Delivery options - requires at least one of onData or webhook
1891
- */
1892
- export declare type DeliveryOptions<T> = RequireAtLeastOne<DeliveryOptionsBase<T>, 'onData' | 'webhook'>;
1893
-
1894
- /**
1895
- * Base delivery options - at least one must be provided
1896
- */
1897
- declare interface DeliveryOptionsBase<T> {
1898
- /** Local callback for processing chunks */
1899
- onData?: (chunk: RecordsChunk<T>, next: () => void) => void | Promise<void>;
1900
- /** Webhook configuration for remote delivery */
1901
- webhook?: WebhookConfig;
1896
+ export declare interface DeliveryOptions<T> {
1897
+ /** Callback for processing delivered chunks */
1898
+ onData: (chunk: RecordsChunk<T>, next: () => void) => void | Promise<void>;
1902
1899
  }
1903
1900
 
1904
1901
  /**
@@ -3498,10 +3495,19 @@ export declare enum ImporterState {
3498
3495
  DESTROYED = "destroyed"
3499
3496
  }
3500
3497
 
3498
+ export declare interface ImportSessionContext {
3499
+ /** Generated session ID for the current import run */
3500
+ sessionId: string;
3501
+ }
3502
+
3501
3503
  export declare type Infer<T extends ExType<unknown, ExBaseDef, unknown>> = T extends ExType<infer Output, ExBaseDef, unknown> ? Output : never;
3502
3504
 
3503
3505
  declare type IPAddressVersion = 'v4' | 'v6' | 'all';
3504
3506
 
3507
+ export declare type LocalStorageOptions = {
3508
+ type: 'local';
3509
+ };
3510
+
3505
3511
  declare interface MultiselectOptions {
3506
3512
  enforceCaseSensitiveMatch?: boolean;
3507
3513
  message?: string;
@@ -3509,17 +3515,17 @@ declare interface MultiselectOptions {
3509
3515
 
3510
3516
  /**
3511
3517
  * Options for the open() method
3512
- * Requires at least one of onData or webhook for delivery
3518
+ * Requires an onData callback for delivery
3513
3519
  */
3514
- export declare type OpenOptions<T> = RequireAtLeastOne<DeliveryOptionsBase<T>, 'onData' | 'webhook'> & {
3520
+ export declare type OpenOptions<T> = DeliveryOptions<T> & {
3515
3521
  /** Number of records per chunk (default: 1000) */
3516
3522
  chunkSize?: number;
3517
3523
  /** Called when all chunks have been processed */
3518
- onComplete?: () => void;
3524
+ onComplete?: (context: ImportSessionContext) => void;
3519
3525
  /** Called when the user cancels the import */
3520
- onCancel?: () => void;
3526
+ onCancel?: (context: ImportSessionContext) => void;
3521
3527
  /** Called when an error occurs */
3522
- onError?: (error: Error) => void;
3528
+ onError?: (error: Error, context: ImportSessionContext) => void;
3523
3529
  /** Called when the importer opens */
3524
3530
  onImporterOpen?: () => void;
3525
3531
  /** Called when the importer closes */
@@ -3528,6 +3534,17 @@ export declare type OpenOptions<T> = RequireAtLeastOne<DeliveryOptionsBase<T>, '
3528
3534
  onStepChange?: (stepId: ExpressCSVStep, previousStepId?: ExpressCSVStep) => void;
3529
3535
  };
3530
3536
 
3537
+ declare interface PersistedImportSessionData {
3538
+ data: PersistedImportSessionPayload;
3539
+ }
3540
+
3541
+ declare type PersistedImportSessionKey = string;
3542
+
3543
+ declare type PersistedImportSessionPayload = {
3544
+ rowData: Record<string, unknown>[];
3545
+ columnKeys: string[];
3546
+ };
3547
+
3531
3548
  declare type PhoneNumberFormat = 'international' | 'national' | 'both';
3532
3549
 
3533
3550
  declare type PhoneNumberOutput = 'e164' | 'formatted' | 'digits';
@@ -3634,6 +3651,10 @@ export declare interface RecordsChunk<T> {
3634
3651
  currentChunkIndex: number;
3635
3652
  /** Total number of records across all chunks */
3636
3653
  totalRecords: number;
3654
+ /** Generated session ID for the current import run */
3655
+ sessionId: string;
3656
+ /** Stable per-chunk idempotency key */
3657
+ chunkIdempotencyKey: string;
3637
3658
  }
3638
3659
 
3639
3660
  declare type RefineBatchResultItem = {
@@ -3665,13 +3686,6 @@ declare type RefineResultItem = {
3665
3686
  };
3666
3687
  };
3667
3688
 
3668
- /**
3669
- * Type helper that requires at least one of the specified keys to be present
3670
- */
3671
- declare type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, Keys>> & {
3672
- [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>;
3673
- }[Keys];
3674
-
3675
3689
  declare interface SelectOption<TValue extends string | number = string | number> {
3676
3690
  label: string;
3677
3691
  value: TValue;
@@ -3684,6 +3698,14 @@ declare interface SelectOptions {
3684
3698
  message?: string;
3685
3699
  }
3686
3700
 
3701
+ export declare type StorageKey = PersistedImportSessionKey;
3702
+
3703
+ export declare type StorageOptions = LocalStorageOptions | CustomStorageOptions;
3704
+
3705
+ export declare type StoredSession = PersistedImportSessionData;
3706
+
3707
+ export declare type StoredSessionData = PersistedImportSessionPayload;
3708
+
3687
3709
  declare type StripBrand<T> = {
3688
3710
  [K in keyof T]: T[K] extends TemplateString<string> ? string : T[K] extends object ? StripBrand<T[K]> : T[K];
3689
3711
  };
@@ -3772,7 +3794,7 @@ declare interface URLOptions {
3772
3794
  message?: string;
3773
3795
  }
3774
3796
 
3775
- export declare function useExpressCSV<TSchema extends ExType<unknown, ExBaseDef, unknown>>({ schema, title, importIdentifier, getSessionToken, debug, preload, theme, colorMode, customCSS, fonts, stepDisplay, previewSchemaBeforeUpload, templateDownload, saveSession, locale, disableStatusStep, }: UseExpressCSVOptions<TSchema>): {
3797
+ export declare function useExpressCSV<TSchema extends ExType<unknown, ExBaseDef, unknown>>({ schema, title, importIdentifier, getSessionToken, debug, preload, theme, colorMode, customCSS, fonts, stepDisplay, previewSchemaBeforeUpload, aiColumnMatching, aiTransform, templateDownload, storage, locale, disableStatusStep, }: UseExpressCSVOptions<TSchema>): {
3776
3798
  open: (options: OpenOptions<Infer<TSchema>>) => void;
3777
3799
  importerState: ImporterState;
3778
3800
  isInitialising: boolean;
@@ -3792,8 +3814,10 @@ export declare interface UseExpressCSVOptions<TSchema extends ExType<unknown, Ex
3792
3814
  fonts?: Record<string, ECSVFontSource>;
3793
3815
  stepDisplay?: 'progressBar' | 'segmented' | 'numbered';
3794
3816
  previewSchemaBeforeUpload?: boolean;
3817
+ aiColumnMatching?: boolean;
3818
+ aiTransform?: boolean;
3795
3819
  templateDownload?: TemplateDownloadOptions<TSchema>;
3796
- saveSession?: boolean;
3820
+ storage?: StorageOptions;
3797
3821
  locale?: DeepPartial<ExpressCSVLocaleInput>;
3798
3822
  disableStatusStep?: boolean;
3799
3823
  }
@@ -3806,31 +3830,6 @@ declare interface ValidatorCheck {
3806
3830
  message?: string;
3807
3831
  }
3808
3832
 
3809
- /**
3810
- * Webhook configuration for remote delivery of results
3811
- */
3812
- export declare interface WebhookConfig {
3813
- /** The URL to send webhook requests to */
3814
- url: string;
3815
- /** Optional HTTP headers to include in the request */
3816
- headers?: Record<string, string>;
3817
- /** HTTP method to use (default: 'POST') */
3818
- method?: 'POST' | 'PUT' | 'PATCH';
3819
- /** Request timeout in milliseconds (default: 30000) */
3820
- timeout?: number;
3821
- /** Number of retry attempts on failure (default: 0) */
3822
- retries?: number;
3823
- /** Arbitrary developer-provided metadata */
3824
- metadata?: Record<string, unknown>;
3825
- /**
3826
- * Whether to wait for the delivery service to confirm the webhook was
3827
- * successfully received before considering the import complete.
3828
- * When false, the import completes as soon as all chunks are queued.
3829
- * Default: false
3830
- */
3831
- awaitWebhookArrival?: boolean;
3832
- }
3833
-
3834
3833
  export declare const x: {
3835
3834
  string: typeof ExString.create;
3836
3835
  number: typeof ExNumber.create;