Skip to content

henrychris/csvimporter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

csv-import-lib

A fluent TypeScript library for parsing and validating product import CSV files. Define rules for each product type, then run your CSV through them — groups are validated, errors are reported per-row with context.

Concepts

Row groups — Rows are grouped by handle. The first row for a handle is the head row; subsequent rows sharing that handle (or rows with a blank handle immediately following) are continuation rows.

Product definitions — You define what a valid group looks like for each product type: which fields are required on the head row, and what kinds of continuation rows are allowed.

Continuation row types — Each continuation row type has a matchWhen predicate, required fields, and forbidden fields. The first matching type wins.

Installation

bun add csv-import-lib     # when published
# or copy src/ directly into your project

Quick Start

import {
    CsvImporter,
    ProductDefinition,
    ContinuationRow,
} from "csv-import-lib";

const importer = new CsvImporter();

// Simple products — no continuation rows allowed
importer.addDefinition(
    ProductDefinition.create("simple")
        .matchWhen((row) => row.type === "simple")
        .requireFields(["handle", "title", "type", "sku"]),
);

// Variable products — each variant is a continuation row
importer.addDefinition(
    ProductDefinition.create("variable")
        .matchWhen((row) => row.type === "variable")
        .requireFields([
            "handle",
            "title",
            "type",
            "sku",
            "option 1 name",
            "option 1 value",
        ])
        .allowContinuationRows(
            ContinuationRow.create()
                .label("variant-row")
                .matchWhen((row) => !!row.sku)
                .requireFields(["handle", "sku", "option 1 value"])
                .forbidFields(["title"]),
        ),
);

// Serialized products — each IMEI is a continuation row
importer.addDefinition(
    ProductDefinition.create("serialized")
        .matchWhen((row) => row.type === "serialized")
        .requireFields(["handle", "title", "type", "sku", "imei"])
        .allowContinuationRows(
            ContinuationRow.create()
                .label("imei-row")
                .matchWhen((row) => !!row.imei && !row.sku)
                .requireFields(["handle", "imei"])
                .forbidFields(["title", "type", "sku", "price", "cost"]),
        ),
);

const result = importer.parse(csvString);

if (result.ok) {
    for (const group of result.valid) {
        console.log(
            group.handle,
            group.definition,
            group.head,
            group.continuations,
        );
    }
} else {
    for (const error of result.errors) {
        console.error(
            `Line ${error.line} [${error.handle}]${error.field ? ` field:${error.field}` : ""}: ${error.message}`,
        );
    }
}

CSV Format

The library expects a header row followed by data rows. The handle column groups rows into products.

handle,title,description,type,quantity,price,cost,sku,imei,barcode,category,tags,status,option 1 name,option 1 value,option 2 name,option 2 value,option 3 name,option 3 value,image src
simple-product,My Product,A description,simple,5,9.99,5.00,SKU-001,,,Electronics,sale,active,,,,,,,
variable-product,My Variable,A description,variable,10,9.99,5.00,SKU-002,,,Clothing,,active,Color,Red,,,,,
variable-product,,,,8,9.99,5.00,SKU-003,,,,,,,Blue,,,,,
serialized-product,My Serialized,A description,serialized,1,199,99,SKU-004,123456789012,,,,,,,,,,,
serialized-product,,,,,,,,987654321098,,,,,,,,,,,

Continuation rows can either repeat the handle or leave it blank — both are treated as belonging to the previous group.

API

new CsvImporter()

Creates a new importer instance. addDefinition() returns this so you can chain:

const importer = new CsvImporter()
  .addDefinition(...)
  .addDefinition(...)

importer.addDefinition(builder: ProductDefinitionBuilder): this

Registers a product definition. Definitions are evaluated in registration order — the first whose matchWhen predicate returns true for a group's head row will own that group.

importer.parse(csv: string): ParseResult

Parses and validates the CSV. Returns:

interface ParseResult {
    ok: boolean; // true if zero errors
    valid: ValidGroup[]; // groups that passed validation
    errors: ValidationError[];
}

interface ValidGroup {
    definition: string; // name of the matched definition
    handle: string;
    head: RawRow;
    continuations: RawRow[];
}

interface ValidationError {
    handle: string;
    line: number;
    field?: ProductRowField; // set when the error is field-specific
    message: string;
}

ProductDefinition.create(name: string)

Starts a new product definition builder.

Method Description
.matchWhen(fn) Predicate against the head row. First match wins.
.requireFields(fields[]) Fields that must be non-empty on the head row.
.allowContinuationRows(builder) Register a continuation row type. Can be called multiple times for multiple types.

ContinuationRow.create()

Starts a new continuation row type builder.

Method Description
.label(name) Human-readable name used in error messages.
.matchWhen(fn) Predicate to identify this row type. First match wins.
.requireFields(fields[]) Fields that must be non-empty.
.forbidFields(fields[]) Fields that must be empty.

Available Fields

These are the column names recognized by ProductRow:

Field Field Field
handle title description
type quantity price
cost sku imei
barcode category tags
status option 1 name option 1 value
option 2 name option 2 value option 3 name
option 3 value image src

All field names are typed as ProductRowField — passing an invalid field name to requireFields or forbidFields will be caught at compile time.

Running Tests

bun run vitest

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors