Every codebase grows a utils/ folder of the same snippets: a retry with backoff, a slugify, a byte formatter, a CSV writer that gets quoting wrong. Lacspace wrote the correct versions of those, once, and shipped them dependency-free — a missing standard library across four small kits.
What’s inside

- Utils Kit — id (unique IDs), retry (backoff), case (camel/snake/kebab).
- Data Kit — xlsx (real Excel), csv (correct parse/format).
- DX Kit — humanize (bytes/dates/numbers), color (hex/rgb/hsl + WCAG contrast).
- Backend Kit — signed-url, pdf (no Chromium), webhooks, idempotency.
- …and extras — cache (LRU + TTL + SWR), money (integer minor-units + Intl), markdown (zero-dep MD→HTML).
Correct, not copy-pasted
import { retry } from '@lacspace/retry';
const data = await retry(() => fetchFlaky(), { retries: 4, minDelay: 300 });
Real PDFs without spinning up Chromium, money that never loses a cent, CSV that quotes correctly, colour with a built-in WCAG check — the un-glamorous things, done right.
Get started
Browse them all at lacspace.com/packages and read the docs at lacspace.com/docs. They’re part of the 100+ package zero-dependency ecosystem.
Install
Every helper is a separate package, so install only the ones your project needs.
npm i @lacspace/id @lacspace/retry @lacspace/case # Utils
npm i @lacspace/xlsx @lacspace/csv # Data
npm i @lacspace/humanize @lacspace/color # DX
npm i @lacspace/signed-url @lacspace/pdf @lacspace/webhooks @lacspace/idempotency # Backend
npm i @lacspace/cache @lacspace/money @lacspace/markdown # extras
Quick start by kit
Utils: ids, retries and string case
@lacspace/retry takes retries, minDelay, shouldRetry and onRetry, and uses exponential backoff with full jitter. It also exports withTimeout and a CircuitBreaker.
import { retry, withTimeout, CircuitBreaker } from '@lacspace/retry';
import { uuidv7, prefixedId } from '@lacspace/id';
import { camelCase, kebabCase } from '@lacspace/case';
const data = await retry(() => fetch(url).then((r) => r.json()), {
retries: 4,
minDelay: 300,
shouldRetry: (err) => isTransient(err), // don't retry 4xx
});
await withTimeout((signal) => fetch(url, { signal }), 5000); // TimeoutError after 5s
const breaker = new CircuitBreaker({ failureThreshold: 5, resetTimeoutMs: 30_000 });
uuidv7(); // time-sortable UUID, index-friendly primary key
prefixedId('user'); // "user_9f8c1a3e..."
kebabCase('XMLHttpRequest'); // "xml-http-request"
Data: Excel and CSV
import { jsonToXlsx, xlsxToJson } from '@lacspace/xlsx';
import { parse, stringify, coerce } from '@lacspace/csv';
const bytes = jsonToXlsx(rows); // Uint8Array, Dates become real Excel dates
const back = await xlsxToJson(uploaded); // reads Excel and Google Sheets exports
const records = coerce(parse(csvText), { auto: true }); // numbers/booleans inferred
const out = stringify(records, { escapeFormulas: true }); // safe for untrusted data
@lacspace/xlsx builds the ZIP-of-XML file format directly, supports multiple sheets, column widths, per-column numFmt and live formulas, and converts between CSV and XLSX. @lacspace/csv handles quoted fields, escaped quotes, embedded newlines, CRLF and BOMs, and has a CsvStreamParser for chunked input.
DX: readable output and accessible colour
import { bytes, duration, relativeTime, pluralize } from '@lacspace/humanize';
import { contrastRatio, wcagLevel, bestTextColor } from '@lacspace/color';
bytes(1536); // "1.5 KB"
duration(90061000); // "1d 1h"
relativeTime(Date.now() - 3.6e6); // "1 hour ago"
pluralize(3, 'city'); // "3 cities"
wcagLevel(contrastRatio('#767676', '#ffffff')); // "AA"
bestTextColor('#1e90ff'); // "#000000"
Backend: links, PDFs, webhooks and exactly-once
import { signUrl, verifyUrl } from '@lacspace/signed-url';
import { invoice } from '@lacspace/pdf';
import { verify } from '@lacspace/webhooks';
import { idempotent } from '@lacspace/idempotency';
const link = await signUrl('https://cdn.example.com/files/report.pdf', { secret, expiresIn: 300 });
const ok = await verifyUrl(request.url, { secret }); // { valid, reason? }, never throws
const pdf = invoice({ brand: 'Acme', number: 'INV-1024', date: '2026-08-23',
items: [{ description: 'Consulting', quantity: 1, rate: 4500 }], currency: '$', taxRate: 13 });
const r = await verify(rawBody, request.headers.get('webhook-signature'), { secret, toleranceSec: 300 });
const { value, replayed } = await idempotent(idempotencyKey, () => chargeCard(order));
Extras: cache, money and Markdown
import { createCache } from '@lacspace/cache';
import { money } from '@lacspace/money';
import { markdownToHtml } from '@lacspace/markdown';
const cache = createCache({ max: 500, ttl: 60_000 });
const user = await cache.wrap(`user:${id}`, () => db.users.find(id)); // concurrent callers share one fetch
money(10, 'USD').allocate([1, 1, 1]).map((m) => m.format()); // ["$3.34", "$3.33", "$3.33"]
markdownToHtml(userMarkdown); // raw HTML in the source is escaped
Why not copy a snippet instead?
The READMEs are candid about where hand-written versions usually go wrong, and these are the cases each package is built to handle:
- Retries without jitter make every client retry at the same moment;
@lacspace/retryuses full jitter and supportsAbortSignal. - Random UUIDs (v4) scatter inserts across a database index. UUID v7 and ULID sort by creation time, and
decodeTime()reads the timestamp back. - CSV breaks on commas and newlines inside quoted cells, and cells starting with
=,+,-or@can run as formulas when opened in a spreadsheet.escapeFormulas: trueprefixes them with a quote. - Money as floats loses cents (
0.1 + 0.2 !== 0.3).@lacspace/moneystores integer minor units, refuses to add different currencies and knows zero-decimal (JPY) and three-decimal (BHD) currencies. - Webhook verification must use the raw body, compare in constant time and reject replays.
@lacspace/webhooksdoes all three and ships presets for Stripe, GitHub and Shopify signatures. - PDF generation usually means a dependency-heavy library or a headless browser.
@lacspace/pdfwrites PDF bytes directly with real Helvetica, Times and Courier metrics, so text wraps and money columns align.
Gotchas worth knowing
- In-memory stores are per process.
@lacspace/idempotencyand the webhook dedupe store default to memory; implement their store interfaces over Redis or SQL for multi-instance apps. - PNG images are not decoded by
@lacspace/pdf. Embed JPEGs withjpegImage(), or decode PNGs elsewhere and pass raw pixels torgbImage(). Money.parse()is best-effort. It can misread ambiguous separators ("1,234"is read as 1.234). For untrusted input, construct from a numeric amount withMoney.oforMoney.fromMinor.- Reading XLSX is async.
xlsxToJsonandreadWorkbookreturn promises because compressed files need inflating; writing is synchronous. - Markdown vs other HTML:
markdownToHtmlalready escapes source HTML. UsesanitizeHtml()only for HTML that came from elsewhere, such as a CMS field.
Each package has its own page on the developer portal, for example retry, xlsx, csv, pdf, webhooks and money, and is published on npm (for example @lacspace/money) under the Lacspace Free Licence v1.0.
Frequently asked questions
What do the Utils, Data, DX and Backend kits cover?
The small, universal helpers every project reaches for — unique IDs, resilient retries and string-case (Utils); real Excel and correct CSV (Data); human-readable formatting and colour maths (DX); and server essentials like signed URLs, PDFs, webhooks and idempotency (Backend). Plus extras like caching, money and markdown. All zero-dependency and isomorphic.
Why not just copy-paste a retry or a slugify?
You can — until the edge cases bite (exponential backoff with jitter, Unicode in slugs, CSV quoting, timezone-correct humanize). These packages are the correct, tested versions of the snippets everyone re-writes, with no dependency tree attached.
Can I really make PDFs without headless Chromium?
Yes — @lacspace/pdf generates real PDFs with zero dependencies and no headless browser (no puppeteer, no pdfkit). It runs on Node, edge and the browser, with batteries-included invoice() and receipt() helpers plus a document builder.
What is idempotency and why do I need it?
It’s how you stop a retried request (a double-clicked “Pay” button, a webhook redelivery) from doing the work twice. @lacspace/idempotency gives you idempotency keys so the same operation runs once, safely.
Is money handling really that tricky?
Floating-point money is a classic bug source. @lacspace/money uses integer minor-units (cents/paisa), safe allocation (splitting a bill without losing a cent) and Intl formatting — so your totals are always exact.








