@ranjeetk25/schema-grid-io
v0.4.1
Published
CSV/XLSX import, export and clipboard parsing for Schema Grid.
Maintainers
Readme
@ranjeetk25/schema-grid-io
CSV/XLSX import and export for Schema Grid, plus the TSV clipboard format used by
@ranjeetk25/schema-grid-ag-grid. The package runs in the browser (for previews and small
exports) and in Node (for server import/export jobs).
Entry points
| Subpath | Runs in | Heavy deps | Exports |
|---|---|---|---|
| @ranjeetk25/schema-grid-io/import | browser + Node | papaparse, exceljs | parseFile, autoMapColumns, validateRows, toChangeBatches, keyOf, chunkRows, createImportJobState, recordChunkResult, buildErrorReportCsv, KEY_COLUMN_TYPES, SheetNotFoundError, ImportConfigError |
| @ranjeetk25/schema-grid-io/export | browser (Blob) + Node (stream) | papaparse, exceljs | buildExport, buildExportBlob, buildExportStream, exportFileName, exportMimeType, HiddenColumnError |
| @ranjeetk25/schema-grid-io/clipboard | anywhere | none | formatForClipboard, formatMatrixForClipboard, parseClipboard |
| @ranjeetk25/schema-grid-io | | | everything above |
./clipboard has no runtime dependencies, so the grid can import it without
pulling in exceljs or papaparse. node:stream is only loaded with a dynamic
import() on the Node stream paths, so browser bundles never include it.
Import pipeline
Call these in order:
parseFile(input, { type?, sheet?, headerRow?, maxRows?, tz? })reads aFile,Blob,ArrayBufferor webReadableStreaminto aParsedTableof strings.- CSV: UTF-8 with the BOM removed; the delimiter (
,;tab|) is detected. - XLSX: numbers use the raw value (not the display text), date-only cells become
YYYY-MM-DD, datetimes become UTC ISO read intz, formulas use their saved result. - Pass
maxRowsfor a preview;truncatedsays whether more rows exist.
- CSV: UTF-8 with the BOM removed; the delimiter (
autoMapColumns(headers, schema, access)suggests a column for each header. Exact label/key matches get confidence 1; fuzzy or abbreviated matches score below 1. Only columns with"edit"access that aren't formulas are ever suggested.validateRows(parsed, mapping, schema, registry, { mode, keyColumnId, unknownOptions, limit?, access? })runs every mapped cell through its field type'sparse, checks required fields and keys, and applies the unknown-option policy ("create"collects labels insummary.newOptions;"reject"makes them cell errors).summary.unknownOptionslists every distinct unknown label per column under either policy, and each failed cell carries anerrorKind("unknownOption","parse"or"required") next to its message. Mapping problems throwImportConfigErrorbefore any row is read. Inupdate/upsertthe key column's type must be inKEY_COLUMN_TYPES(text, longText, email, phone or url).toChangeBatches(report, existingRowsByKey, { schema, registry, mode, keyColumnId })returns anImportPlan: flatcreates(split them withchunkRows), updateChangeBatches (source: "import", at most 500 rows each),rejectedrows, andunchangedSourceRows(rows identical to what is stored, which are left out).- On the server, create any
newOptionsfirst, send each chunk, and feed every result torecordChunkResultstarting fromcreateImportJobState(total, plan.rejected, plan.unchangedSourceRows.length).buildErrorReportCsv(state, parsed)produces a downloadable CSV of the failed rows.
existingRowsByKey must be built with keyOf
The server job runner must build the lookup map with the exported keyOf(value, keyColumn, registry),
which is the field type's format, trimmed and lowercased. The same function is used for
duplicate-key detection in validateRows and for matching in toChangeBatches. If two stored
rows produce the same key, the server must report them instead of letting one overwrite the other.
Export
buildExport({ columns, registry, rows, format, tz, fileName, access, sheetName? })
returns a Blob in the browser and a Node Readable in Node. buildExportBlob and
buildExportStream pick a path explicitly. rows can be an array or an AsyncIterable,
which the stream paths read lazily.
- XLSX cells are typed: numbers and currency are numbers with a number format, dates and
datetimes are Excel dates (datetimes shown as clock time in
tz), booleans are booleans, URLs are hyperlinks. The header row is bold and frozen; widths come fromColumnDef.width. - CSV starts with a UTF-8 BOM and uses
\r\n. Text that could run as a spreadsheet formula (starting with=,+,-,@, tab or CR/LF) gets a leading'. Numbers, dates, phone numbers and booleans are written unchanged.
Access fails closed
Every export and formatForClipboard(..., { access }) throws HiddenColumnError before
writing anything if any requested column is "hidden" or missing from the access map.
Pass only the columns the user can read.
Timezones
Pass the same tz to export and to parseFile. XLSX has no timezones: export writes a
datetime as the clock time in tz, and import reads a clock time back as an instant in tz.
Clipboard
formatMatrixForClipboard / parseClipboard use Excel's TSV rules: cells containing a tab,
newline or " are quoted, "" is a literal quote, one trailing row break is dropped, and
ragged rows are padded. formatForClipboard(rows, columns, registry) formats each cell with
its field type's format.
Core integration
Every core contract comes from @ranjeetk25/schema-grid-core through one shim,
src/internal/core.ts (the only file allowed to import core). It imports types only,
so no core code is bundled into ./clipboard.
Open item (TODO(core)): getSelectOptions reads config.options itself; replace it
with a core option accessor if core exports one.
Import applies its own unknown-option policy (unknownOptions: "create" | "reject") to
select, creatableSelect and multiSelect columns, independent of each column's
allowCreate setting; other types' pendingOptions are honoured through the same policy.
