Skip to content

The data model

Internally the explorer works with one canonical shape:

ts
interface DocumentItem {
  id: string;
  name: string;
  type: 'file' | 'folder';
  parentId?: string | null;

  mimeType?: string;
  extension?: string;
  size?: number;
  createdAt?: string | number | Date;
  updatedAt?: string | number | Date;

  viewUrl?: string;
  downloadUrl?: string;

  /** Anything you want to carry through and display. */
  metadata?: Record<string, unknown>;

  /** The untouched source object this was mapped from. */
  raw?: unknown;
}

If your data already looks like this, you need no mapper at all:

tsx
<DocumentExplorer data={documents} />

Otherwise, see Mapping your data.

Why flat, not nested

The internal truth is flat rows plus parentId, not a nested children tree. Flat suits how document data actually behaves: it paginates, it virtualizes, it lazy-loads, and you can update a single row without rebuilding a subtree.

Nested input is still accepted at the boundary and flattened for you, so you can pass whichever shape your API returns.

ts
// Both of these work.
const flat = [
  { id: '1', name: 'Engineering', type: 'folder' },
  { id: '2', name: 'spec.pdf', type: 'file', parentId: '1' },
];

const nested = [
  { id: '1', name: 'Engineering', type: 'folder',
    children: [{ id: '2', name: 'spec.pdf', type: 'file' }] },
];

Metadata

Anything that is not part of the canonical shape belongs in metadata, and can then be sorted, searched and displayed:

ts
metadata: { owner: 'Nitesh', status: 'approved', department: 'Engineering' }
tsx
<DocumentExplorer
  columns={['name', { key: 'metadata.owner', label: 'Owner' }, 'size']}
  search={{ searchableFields: ['metadata.owner'] }}
/>

Messy data is repaired, not fatal

Real payloads contain bad rows. The normalizer never throws; it repairs and reports:

ProblemWhat happens
Duplicate idThe later row is dropped.
Missing idA synthetic id is assigned.
parentId points at a row that does not existTreated as a root.
A row is its own parentThe link is cut.
A cycle between rowsThe chain is broken.
A file is used as a parentIt is promoted to a folder.

Each produces a NormalizeWarning, which you can surface if you want to know about upstream data problems:

ts
import { normalize } from '@document-explorer/core';

const { warnings } = normalize(apiResponse, { mapper });
warnings.forEach((w) => console.warn(w.code, w.message));

One bad record must not take down the explorer.

Released under the MIT License.