
Security News
Axios Supply Chain Attack Reaches OpenAI macOS Signing Pipeline, Forces Certificate Rotation
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.
tinybase-zod
Advanced tools
A small TypeScript library that wraps a TinyBase Store with a Zod-backed typed API.
You describe your data model once as a Zod schema:
tables: { [tableId]: z.object({ ...cells }) }values: z.object({ ...valueIds })…and createTypedStore(store, schema) returns a typedStore whose get* methods decode and whose set* methods encode, while keeping TinyBase’s API shape (tables/rows/cells/values + listeners + transactions).
Public entrypoint: src/index.ts (re-exports codec, store, and type).
TinyBase cells are expected to be scalars, so this wrapper enforces a simple contract:
string | number | boolean | null | undefinedundefined means “delete / absent”If a schema encodes to a non-primitive (for example a plain z.object(...), z.array(...), z.date(), z.bigint(), etc.), writes will throw with a message like:
Invalid encoded value for TinyBase storage at ... Expected string|number|boolean|null (or undefined to delete), got object.To store structured data, you must wrap it in an explicit codec.
import { createStore } from "tinybase";
import z from "zod";
import {
createTypedStore,
createReadonlyTypedStore,
json,
dateAsIso,
dateAsNumberMs,
} from "tinybase-zod";
const userRow = z.object({
name: z.string(),
age: z.number(),
// Structured cells must be wrapped so they encode to strings.
prefs: json(z.object({ theme: z.enum(["light", "dark"]) })),
tags: json(z.array(z.string())),
// Non-JSON types should use explicit codecs too.
createdAt: dateAsIso,
// Or persist as numbers (ms since epoch).
createdAtMs: dateAsNumberMs,
});
const schema = {
tables: {
users: userRow,
},
values: z.object({
selectedUserId: z.string().nullable(),
}),
} as const;
const store = createStore();
const typed = createTypedStore(store, schema);
const readonly = createReadonlyTypedStore(store, schema);
// Or: const readonly = typed.asReadonly();
// Tests typically initialize tables/values explicitly.
store.setTables({ users: {} });
store.setValues({});
typed.setRow("users", "u1", {
name: "Ava",
age: 34,
prefs: { theme: "dark" },
tags: ["admin"],
});
// Reads are decoded.
console.log(typed.getCell("users", "u1", "prefs")); // { theme: "dark" }
// Underlying TinyBase storage contains JSON strings for json(...) fields.
console.log(store.getCell("users", "u1", "prefs")); // '{"theme":"dark"}'
A StoreSchema is:
tables: a map of table ids to z.object({...}) row schemasvalues: a single z.object({...}) for global valuescreateTypedStore does not auto-modify your schema. It uses it as-is.
z.record(z.string(), rowSchema).getTable, getRow, getCell, getValue return decoded values.setTable, setRow, setCell, setValue encode before writing.setContent([tables, values]) encodes both parts and replaces the store content (see TinyBase docs: setContent).On every write, the wrapper validates that encoded cells are storage scalars.
undefined is treated as deletionundefined cells are stripped.undefined, the wrapper deletes it.This means you can model “optional deletes” using codecs that sometimes encode to undefined.
getValue matches the Zod schema typegetValue(valueId) returns exactly what your Zod schema describes for that value id:
z.string()), calling getValue when the underlying store value is missing will throw a Zod error.z.string().optional()), missing values return undefined.z.string().default("x")), missing values return the default.This removes the extra | undefined that was previously added by the typed wrapper.
Listener callbacks wrap the underlying TinyBase listener and:
typedStore instead of the raw storenew/old valuesgetCellChange(...) / getValueChange(...) results when those helper functions are availableMutator listeners (mutator: true) behave like TinyBase: they run before non-mutators and can safely update state.
If you want to expose a typed store to consumers that should not be able to write, use:
createReadonlyTypedStore(store, schema), ortyped.asReadonly()Readonly typed stores are type-level only (developer ergonomics):
json(...)src/codec.ts exports a helper:
json(schema) wraps any JSON-serializable schema and stores it as a JSON string.It is implemented via z.codec(...) with JSON.stringify / JSON.parse.
If you need null/undefined support, apply wrappers outside the codec:
const maybePrefs = json(z.object({ theme: z.string() })).optional();
const nullablePrefs = json(z.object({ theme: z.string() })).nullable();
Use it for:
z.object(...))z.array(...))For non-JSON types, define your own storage representation:
import { z } from "zod";
export const bigintAsString = z.codec(z.string(), z.bigint(), {
encode: (b) => b.toString(),
decode: (s) => BigInt(s),
});
This repo also ships date codecs in src/codec.ts:
dateAsIso (ISO string)dateAsNumberMs / dateAsNumberSeconds (numbers)This wrapper intentionally avoids a common TinyBase behavior: creating/patching rows implicitly via setCell.
typed.setCell(...) throws if the row does not already exist.
setRow(...) first.typed.setRow(...) expects a schema-valid row.
setCell(...) after creating the row.If you access the underlying TinyBase store directly, you’ll see encoded storage values (for example JSON strings), not decoded runtime objects.
If your underlying store is a TinyBase MergeableStore, the typed wrapper exposes:
typed.setDefaultContent([tables, values]) (and the function form)It accepts typed content (decoded shapes) and encodes it according to your schema before forwarding to the underlying TinyBase method. See TinyBase docs: setDefaultContent.
Because writes call .encode(), any schema that can’t encode will fail at runtime.
Common causes:
z.object, z.array, etc.) encode to objects/arrays → wrap with json(...).z.preprocess, .transform) are not encodable in Zod v4 → use a bidirectional z.codec(...) instead.bun install
bun test
bun run check
bun run build
src/store.ts: implementation of createTypedStoresrc/codec.ts: storage codecs like json(...) and dateAsIsosrc/type.ts: the TypeScript type-level surface for TypedStore<Schema>src/example.ts: a small usage sketchsrc/*.test.ts: runtime behavior testssrc/*.type.test.ts: type-level testsFAQs
A typed TinyBase Store wrapper with a Zod-backed encode/decode API.
The npm package tinybase-zod receives a total of 1 weekly downloads. As such, tinybase-zod popularity was classified as not popular.
We found that tinybase-zod demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.

Security News
Open source is under attack because of how much value it creates. It has been the foundation of every major software innovation for the last three decades. This is not the time to walk away from it.

Security News
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.