The universal data toolkit. Define once, use everywhere.
datapack is a zero-dependency TypeScript library that unifies the jobs normally split across a validator, a form library, an HTTP client, and a state store. Define a schema once and reuse it to validate, transform, serialize, fetch, and store data through one cohesive API.
import { dp } from 'datapack';
const User = dp({
name: dp.string().min(1),
email: dp.string().email(),
age: dp.number().int().min(0).optional(),
});
type User = dp.infer<typeof User>;
const user = User.parse(rawInput); // validate
const pack = User.pack(rawInput); // wrap for serialization
const body = pack.toFormData(); // send as multipart
const json = pack.toJSON(2); // or JSON
const data = await dp.fetch('/api/users/1', { schema: User }); // fetch + validate
const store = dp.store(User, user); // reactive stateMost apps end up with the same shape of data defined four or five times: once in a Zod schema, once in a TypeScript interface, once in a form library, once in an API client, once in a state store. These copies drift. datapack keeps one source of truth and gives you the whole pipeline.
- One schema, many surfaces. The same
Schemavalidates input, narrows types, serializes to JSON / FormData / URL params / CSV / Headers / Map, parses env vars, binds REST endpoints, and backs a reactive store. - Tiny and dependency-free. No runtime dependencies. Tree-shakeable ESM + CJS builds.
- TypeScript-first. Full type inference with
dp.infer<typeof Schema>. No codegen. - Familiar. If you've used Zod, the field API will feel immediately natural.
npm install datapackRequires Node.js 18+ (uses native fetch, FormData, structuredClone).
const Post = dp({
id: dp.number().int(),
title: dp.string().min(1).max(200),
tags: dp.array(dp.string()),
status: dp.enum(['draft', 'published']),
publishedAt: dp.date().optional(),
author: dp.object({
name: dp.string(),
email: dp.string().email(),
}),
});
const result = Post.safeParse(input);
if (!result.ok) {
console.error(result.errors.issues);
}Schemas compose with .partial(), .pick(...), .omit(...), .extend({...}), and .merge(other).
const pack = User.pack(data);
pack.toObject(); // plain object (structured clone)
pack.toJSON(); // JSON string
pack.toFormData(); // FormData (nested keys flattened)
pack.toURLParams(); // query string
pack.toHeaders(); // Headers (primitive values only)
pack.toCSV(); // single-row CSV with header
pack.toMap(); // Map<string, unknown>
pack.toEntries(); // [key, value][]And back the other way:
DataPack.fromJSON(jsonString, User);
DataPack.fromFormData(formData, User);
DataPack.fromURLParams(searchString, User);const user = await dp.fetch('/api/users/1', {
schema: User,
retry: 3,
timeout: 5000,
cacheTTL: 30_000,
});Features: retries with exponential backoff, timeouts, GET-response caching, automatic JSON / text detection, and schema validation on every response.
const users = dp.endpoint('/api/users', User, { baseURL: 'https://example.com' });
await users.list();
await users.get(1);
await users.create({ name: 'Ada', email: 'ada@example.com' });
await users.update(1, { name: 'Ada L.' });
await users.patch(1, { name: 'Ada L.' });
await users.remove(1);const store = dp.store(User, { name: 'Ada', email: 'ada@example.com' });
store.get();
store.update({ name: 'Ada L.' }); // merges + revalidates
store.set(nextState); // replaces + revalidates
store.reset();
const unsub = store.subscribe((state, prev) => console.log(state));
store.select((s) => s.email, (email) => console.log('email changed:', email));All mutations are validated through the schema, so the store can never hold invalid state.
const config = dp.env({
PORT: dp.number().default(3000),
DB_URL: dp.string(),
DEBUG: dp.boolean().default(false),
});Reads from process.env, coerces strings into the right types, and throws a single aggregated error if anything is missing or invalid.
Everyday data helpers that pair well with schemas:
dp.pick, dp.omit, dp.merge, dp.clone, dp.equals, dp.diff,
dp.flatten, dp.unflatten, dp.hash, dp.mapValues, dp.filterKeys,
dp.groupBy, dp.sortBy, dp.uniqueBy| Area | Entry point |
|---|---|
| Schema | dp({...}), Schema |
| Fields | dp.string/number/boolean/date/array/enum/object |
| Serialize | schema.pack(data), DataPack |
| Fetch | dp.fetch, dp.endpoint, dp.clearCache |
| Store | dp.store(schema, initial) |
| Env | dp.env(shape) |
| Utilities | dp.pick, dp.omit, dp.merge, ... |
| Errors | DatapackError, ValidationIssue |
| Types | dp.infer<typeof Schema> |
MIT