A powerful, flexible schema definition and validation system for TypeScript with seamless multi-database support.
MANIC Schema is a revolutionary TypeScript-first ORM/ODM that lets you define your data model once and seamlessly use it across multiple databases — MongoDB, Neo4j, PostgreSQL, and more. Built from the ground up with modern best practices:
- Define Once, Use Anywhere: Define schema in TypeScript, deploy to any supported database
- Multi-Database Architecture: Mix and match databases for optimal performance
- Cross-Database Relationships: Maintain relationships between entities in different databases
- GraphQL Ready: Auto-generate GraphQL schema from your entities
- Database-Specific Optimizations: Leverage native database features when needed
- Type-Safe: Full TypeScript support with proper types throughout
- Clean Design: Explicit dependencies, no global state, no magic
- Adapter Pattern: Extensible adapter system for database integrations
- Decorator-Based: Intuitive TypeScript decorators for schema definition
- 100% Test Coverage: Comprehensive test suite with property-based testing
Database | Status | Features |
---|---|---|
MongoDB | ✅ Stable | Collections, indexes, embedding |
Neo4j | ✅ Stable | Node labels, relationship types, Cypher queries |
PostgreSQL | ✅ Stable | Tables, columns, indexes, joins |
SQLite | 🚧 In Progress | Basic functionality |
Redis | 🚧 In Progress | Basic functionality |
DynamoDB | 🗓️ Planned | - |
Firestore | 🗓️ Planned | - |
Define your entities with TypeScript decorators:
@Entity()
class Post {
@Id()
id: string;
@Property({ required: true })
title: string;
@Property()
content: string;
@Property()
createdAt: Date;
@ManyToOne({
target: () => User,
inverse: "posts",
name: "AUTHORED",
})
author: User;
@ManyToMany({
target: () => Tag,
inverse: "posts",
name: "HAS_TAG",
})
tags: Tag[] = [];
}
Optimize for specific databases when needed:
// MongoDB specific
@Collection("blog_posts")
@MongoIndex({ title: "text", content: "text" })
class Post {
/* ... */
}
// Neo4j specific
@Labels(["Content", "BlogPost"])
@RelationshipType("TAGGED_WITH")
class Post {
/* ... */
}
// PostgreSQL specific
@Table("blog_posts")
@Column({ name: "post_content", type: "text" })
@PgIndex({ name: "idx_post_title", columns: ["title"] })
class Post {
/* ... */
}
Explicit dependencies, no global state:
// Create registry and builder
const registry = new MetadataRegistry();
const builder = new SchemaBuilder(registry);
// Register your entities
builder.registerEntities([User, Post, Comment, Tag]);
Use different databases for different entity types:
// Create adapters for different databases
const mongoAdapter = new MongoDBAdapter(
registry,
"mongodb://localhost:27017/blog"
);
const neo4jAdapter = new Neo4jAdapter(registry, "bolt://localhost:7687");
const pgAdapter = new PostgreSQLAdapter(
registry,
"postgres://localhost:5432/blog"
);
// Create repositories with appropriate adapters
const userRepo = new Repository<User>(User, mongoAdapter);
const postRepo = new Repository<Post>(Post, neo4jAdapter);
const tagRepo = new Repository<Tag>(Tag, pgAdapter);
Consistent interface across all databases:
// Find by ID
const user = await userRepo.findById("user123");
// Query by criteria
const posts = await postRepo.find({ completed: true });
// Create and save
const tag = new Tag();
tag.name = "TypeScript";
await tagRepo.save(tag);
// Delete
await userRepo.delete("user123");
Automatic GraphQL schema generation:
// Generate GraphQL schema from entities
import { generateGraphQLSchema } from "@manic.codes/schema/graphql";
const typeDefs = generateGraphQLSchema(registry);
const resolvers = generateResolvers(registry, {
User: userRepo,
Post: postRepo,
Tag: tagRepo,
});
// Create Apollo Server
const server = new ApolloServer({
typeDefs,
resolvers,
});
import {
Entity,
Id,
Property,
ManyToOne,
ManyToMany,
MetadataRegistry,
SchemaBuilder,
MongoDBAdapter,
Neo4jAdapter,
PostgreSQLAdapter,
Repository,
} from "@manic.code/schema";
// Define entities
@Entity()
class User {
@Id()
id: string;
@Property({ required: true })
name: string;
@Property({ required: true, unique: true })
email: string;
@OneToMany({ target: () => Post, inverse: "author" })
posts: Post[] = [];
}
@Entity()
class Post {
@Id()
id: string;
@Property({ required: true })
title: string;
@Property()
content: string;
@Property()
createdAt: Date = new Date();
@ManyToOne({ target: () => User, inverse: "posts" })
author: User;
@ManyToMany({ target: () => Tag, inverse: "posts" })
tags: Tag[] = [];
}
@Entity()
class Tag {
@Id()
id: string;
@Property({ required: true, unique: true })
name: string;
@ManyToMany({ target: () => Post, inverse: "tags" })
posts: Post[] = [];
}
// Set up the registry and builder
const registry = new MetadataRegistry();
const builder = new SchemaBuilder(registry);
builder.registerEntities([User, Post, Tag]);
// Create database adapters
const mongoAdapter = new MongoDBAdapter(
registry,
"mongodb://localhost:27017/blog"
);
const neo4jAdapter = new Neo4jAdapter(registry, "bolt://localhost:7687");
const pgAdapter = new PostgreSQLAdapter(
registry,
"postgres://localhost:5432/blog"
);
// Create repositories with different adapters
const userRepo = new Repository<User>(User, mongoAdapter);
const postRepo = new Repository<Post>(Post, neo4jAdapter);
const tagRepo = new Repository<Tag>(Tag, pgAdapter);
// Create entities with relationships across databases
async function createBlogPost() {
// Create user in MongoDB
const user = new User();
user.id = "user1";
user.name = "John Doe";
user.email = "john@example.com";
await userRepo.save(user);
// Create tags in PostgreSQL
const tag1 = new Tag();
tag1.id = "tag1";
tag1.name = "TypeScript";
await tagRepo.save(tag1);
const tag2 = new Tag();
tag2.id = "tag2";
tag2.name = "ORM";
await tagRepo.save(tag2);
// Create post in Neo4j with relationships to MongoDB and PostgreSQL
const post = new Post();
post.id = "post1";
post.title = "Multi-Database ORM with TypeScript";
post.content = "This is amazing...";
post.author = user;
post.tags.push(tag1, tag2);
await postRepo.save(post);
return post;
}
npm install @manic.code/schema
MIT © Zach Winter