Shapes is a tiny, schema and data modeling library for TypeScript.
- ✨ Drop-in replacement for Zod
- 📦 Tiny (~3.4KB) bundle size impact - ideal for frontend or serverless environments
- 🚀 Fast compilation speed - no more sluggish IDE or compile times with types optimized for TypeScript 5
- 🟩 Complementary "shapes" for modeling databases and APIs, such as DynamoDB, Deno KV and more.
import s from "shapes";
class Post = s.class({
title: s.string(),
content: s.string(),
likes: s.number(),
}) {}
class User extends s.class({
username: s.string(),
age: s.number(),
email: s.string().optional(),
posts: s.array(Post),
parent: s.this<User>().optional(),
}) {}
const user = User.parse({ .. })
Library | esbuild | esbuild (minified) | WebPack |
---|---|---|---|
Shapes 🚀 | 6.9K | 3.4K | 3.4K |
Zod | 107K | 52K | 52K |
Effect | 332K | 134K | 116K |
Arktype | 110K | 46K | 46K |
Typebox | 86K | 36K | 33K |
Yup | 88K | 40K | 34K |
Click to learn how we achieve a small bundle
This is a collapsible section.
Feature | Shapes | Zod | Effect | Arktype | Typebox | Yup |
---|---|---|---|---|---|---|
Primitives | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
Objects | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
Arrays | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
Validation | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
Recursive | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
This | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ |
Class | ✅ | ❌ ? | ✅ | ✅ | ❌ | ❌ |
Shapes is a library for schema validation and data modeling. It can be used to parse data in APIs and model database schemas.
const str = s.string();
const num = s.number();
const User = s.object({
username: s.string(),
age: s.number(),
});
class User extends s.class({
username: s.string(),
age: s.number(),
}) {}
s.this()
can be used for defining circular self-referential types - i.e. object types that contain references to other instances of itself.
type User = s.infer<typeof User>;
const User = s.object({
username: s.string(),
age: s.number(),
parent: s.this().optional(),
});
class User extends s.class({
username: s.string(),
age: s.number(),
parent: s.this<User>().optional(),
}) {}
In class types, due to limitations in TypeScript, you need to annotate s.this()
with the "User" type.
Shapes is all about modeling data and provides complementary libraries wo
The DynamoDB Shape offers a type-safe interface for single table design in DynamoDB.
First, you create one or more Entities
import { entity, table } from "shapes/aws/dynamodb";
import s from "shapes";
class Chat extends entity("Chat", {
pk: ["chatId"],
attributes: {
chatId: s.string(),
userId: s.string(),
title: s.string().optional(),
createTime: s.string(),
updateTime: s.string(),
},
}) {}
class Message extends entity("Message", {
pk: ["chatId"],
sk: ["messageId"],
attributes: {
chatId: s.string(),
messageId: s.string(),
userId: s.string(),
message: s.string(),
createTime: s.string(),
updateTime: s.string().optional(),
},
}) {}
Then, define a table
for storing those Entities.
// define a class to represent the table and its entities
class ChatTable extends table({
entities: {
Chat,
Message,
},
}) {}
// instantiate that class to receive a client
const chatTable = new ChatTable({
tableName: "test_table",
client: DocumentDBClient.from(new DynamoDBClient({})),
});
Now, you can read and write data to that table with a simple, type-safe, schema-aware syntax:
await chatTable.put(
new Chat({
chatId: "chat-id",
userId: "user-id",
title: "Chat Title",
createTime: new Date().toISOString(),
updateTime: new Date().toISOString(),
}),
new Message({
chatId: "chat-id",
messageId: "message-id",
userId: "user-id",
message: "Hello, world!",
createTime: new Date().toISOString(),
updateTime: new Date().toISOString(),
})
);
// based on key, we know that a Chat will be returned
const chat: Chat | undefined = await chatTable.get({
chatId: "chat-id",
});
// based on key, we know that a Message will be returned
const message: Message | undefined = await chatTable.get({
chatId: "chat-id",
messageId: "message-id",
});
// based on query, type-safe awareness that either a Chat | Message will be returned
const chatOrMessage: QueryResult<Chat | Message> = await chatTable.query({
chatId: "chat-id",
});
// based on query, we know that only Message data types will be returned
const message: QueryResult<Message> = await chatTable.query({
chatId: "chat-id",
messageId: {
$beginsWith: "message-id",
},
});
Coming Soon! 🔧
Coming Soon! 🔧
Coming Soon! 🔧
Coming Soon! 🔧