agentsclimarketplace

Import data from csv

Skill kennguyen887/agent-foundation/skills/import-data-from-csv

Claude Code skills marketplace — backend & frontend engineering conventions + step-by-step third-party integration recipes: Stripe, Rapyd, CyberSource, UOB & wallet payments, Singpass/Keycloak OIDC & 3-D Secure, Twilio SMS, Docker & CI/CD. NestJS/TypeScript + React, language-flexible.

Install
npx -y skills add kennguyen887/agent-foundation --skill import-data-from-csv

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Use when building a bulk CSV/spreadsheet import endpoint — streaming parse, per-row validation with a row-numbered error report, data normalization (Excel quotes, multi-format dates), atomic chunked upsert in a transaction, partial-success response, and fan-out to workers for large files. NestJS/TypeORM reference, framework-flexible.

SKILL.md

4.3 KB, as published. Nobody here has run it

Import data from CSV

A bulk-import endpoint (members, products, locations, …) that ingests a CSV/spreadsheet and reports exactly which rows failed. Examples NestJS/TS, neutral listing domain. principle → ▸ Example▸ Other stacks.

Core principle

Validate per row; never all-or-nothing-silently. Parse → validate each row → collect valid rows AND a row-numbered error list → write the valid ones atomically → return a partial-success report (N imported, M failed + why). The user must learn which row failed and why, not just "import failed".

1. Parse — streaming + normalized

  • Stream the CSV (don't load a huge file fully into memory). Validate the header row first (required columns present) before processing data rows; skip empty rows.
    csv({ delimiter: 'auto', trim: true }).fromString(normalized)
      .on('header', (h) => assertRequiredHeaders(h))
      .subscribe((row, lineNo) => { /* §2 */ }, onError, () => resolve({ valid, errors }));
    
  • Normalize spreadsheet quirks first: collapse Excel triple-quotes ("""x""""x"), trim cells, blank → undefined, upper-case headers for matching.
  • Parse dates defensively — try a list of accepted formats, then timezone-normalize; if none match, record a row error (don't silently store an invalid/shifted date):
    const d = ['DD/MM/YYYY','YYYY-MM-DD','D-MMM-YY'].map((f) => dayjs(v, f, true)).find((x) => x.isValid());
    return d ? d.tz(TZ) : null;   // null → row error
    

2. Validate each row → collect errors with row numbers

Map the row to a DTO and validateSync it (class-validator); on failure push a structured row error (line number + key + flattened messages) and continue — don't abort the whole file:

const dto = mapRowToDto(row);
const errs = validateSync(dto, { whitelist: true });
if (errs.length) { rowErrors.push({ row: lineNo, key: dto.code, messages: flatten(errs) }); return; }
valid.push(dto);

3. Write valid rows atomically, in chunks

  • Wrap the write in a transaction; upsert in chunks of N (don't build one giant statement) via the ORM's bulk upsert; dedupe rows first (Map by business key).
    for (const part of chunk(valid, 500)) await em.upsert(Listing, part, ['orgId', 'code']);
    
  • Log an import-summary row (who, file, counts) for audit.

4. Respond with partial success

Return { imported: valid.length, failed: rowErrors.length, errors: rowErrors } — a 200 report, not a 400 that loses the detail. The client shows the user exactly which rows to fix.

5. Large files → fan out in controlled batches

Don't process a huge file inline — chunk rows into batches and dispatch to workers, grouping batches to cap concurrency, with audit-friendly names:

const batches = chunk(rows, BATCH_SIZE);     // e.g. 100 rows/batch
const groups  = chunk(batches, GROUP_SIZE);  // e.g. 5 batches at a time → bounded concurrency
for (const group of groups)
  await dispatchBatch({ event: EVENT.bulkImport, payloadList: group.map((b, i) => ({ fileName: `${name}-part-${i}`, data: b })) });

Each batch job must be idempotent (see background-jobs-and-caching — DB-lock or Redis SET NX). ▸ Other stacks: same shape everywhere — stream-parse, per-row validate + error list, chunked bulk upsert in a tx, partial-success report, fan out big files to a queue.

Verification

  • The endpoint returns a per-row error report (row number + reason), not a single opaque failure.
  • Valid rows are written atomically and in chunks (no one-giant-insert, no per-row round trips).
  • Header validated up front; dates parsed against an explicit format list; large files fan out to idempotent batch jobs.

Related

  • background-jobs-and-caching (batch jobs + idempotency) · write-service-code (transactions, validation) · code-conventions.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.