Skip to content

nuvix-dev/database

Repository files navigation

@nuvix/db

npm version TypeScript License Build Status

A modular and performant database library for Nuvix, with internal complexity abstracted from developers. Built with TypeScript, this library provides a high-level interface for PostgreSQL databases with support for relationships, validation, caching, and more.

Features

🚀 High Performance - Optimized queries and connection pooling
🔒 Type Safe - Full TypeScript support with generated types
📊 Relationships - OneToOne, OneToMany, ManyToOne, and ManyToMany relationships
🛡️ Security - Built-in authorization and document-level permissions
Validation - Comprehensive data validation and structure checking
🎯 Query Builder - Fluent query interface with filters, sorting, and pagination
📇 Indexing - Support for key, unique, and fulltext indexes
🔄 Transactions - ACID transaction support
💾 Caching - Integrated caching layer for improved performance
🏢 Multi-tenancy - Built-in support for shared tables and tenant isolation
📝 Migrations - Schema migration support

Installation

npm install @nuvix/db
# or
yarn add @nuvix/db
# or
bun add @nuvix/db

Quick Start

import {
  Database,
  Adapter,
  Doc,
  AttributeEnum,
  Permission,
  Role,
} from "@nuvix/db";
import { Memory } from "@nuvix/cache";

// Create database adapter
const adapter = new Adapter({
  connectionString: "postgres://user:pass@localhost:5432/mydb",
});

// Initialize database
const db = new Database(adapter, new Memory());

// Create database schema
await db.create();

// Create a collection
await db.createCollection({
  id: "users",
  attributes: [
    new Doc({
      $id: "name",
      key: "name",
      type: AttributeEnum.String,
      size: 100,
      required: true,
    }),
    new Doc({
      $id: "email",
      key: "email",
      type: AttributeEnum.String,
      size: 255,
      required: true,
    }),
    new Doc({
      $id: "age",
      key: "age",
      type: AttributeEnum.Integer,
      size: 4,
    }),
  ],
  permissions: [Permission.create(Role.any())],
});

// Create a document
const user = await db.createDocument(
  "users",
  new Doc({
    name: "John Doe",
    email: "john@example.com",
    age: 30,
    $permissions: [Permission.read(Role.any()).toString()],
  }),
);

// Read a document
const retrieved = await db.getDocument("users", user.getId());

// Query documents
const users = await db.find("users", (qb) => qb.equal("age", 30).limit(10));

// Update a document
await db.updateDocument(
  "users",
  user.getId(),
  new Doc({
    age: 31,
  }),
);

// Delete a document
await db.deleteDocument("users", user.getId());

Core Concepts

Collections and Attributes

Collections are like tables in traditional databases. Each collection has attributes that define the structure of documents:

// Define collection attributes
const attributes = [
  new Doc({
    $id: "title",
    key: "title",
    type: AttributeEnum.String,
    size: 200,
    required: true,
  }),
  new Doc({
    $id: "content",
    key: "content",
    type: AttributeEnum.String,
    size: 5000,
  }),
  new Doc({
    $id: "published",
    key: "published",
    type: AttributeEnum.Boolean,
    default: false,
  }),
  new Doc({
    $id: "tags",
    key: "tags",
    type: AttributeEnum.String,
    size: 50,
    array: true, // Array of strings
  }),
];

Supported Attribute Types

  • AttributeEnum.String - Text with specified size limit
  • AttributeEnum.Integer - Integer numbers with optional size
  • AttributeEnum.Float - Floating point numbers
  • AttributeEnum.Boolean - True/false values
  • AttributeEnum.Json - JSON objects
  • AttributeEnum.Uuid - UUID strings
  • AttributeEnum.Timestamptz - Timestamps with timezone
  • AttributeEnum.Relationship - References to other documents

Relationships

Create relationships between collections:

// One-to-Many: User has many Posts
await db.createRelationship({
  collectionId: "users",
  relatedCollectionId: "posts",
  type: RelationEnum.OneToMany,
  id: "posts",
  twoWay: true,
  twoWayKey: "author",
});

// Many-to-Many: Posts have many Tags
await db.createRelationship({
  collectionId: "posts",
  relatedCollectionId: "tags",
  type: RelationEnum.ManyToMany,
  id: "tags",
  twoWay: true,
  twoWayKey: "posts",
});

Querying

Use the fluent query builder for complex queries:

// Simple queries
const users = await db.find("users", (qb) =>
  qb
    .equal("status", "active")
    .greaterThan("age", 18)
    .limit(50)
    .offset(0)
    .orderBy("name", "ASC"),
);

// Complex queries with multiple conditions
const posts = await db.find("posts", (qb) =>
  qb
    .equal("published", true)
    .search("title", "typescript")
    .between("created_at", "2024-01-01", "2024-12-31")
    .contains("tags", ["tutorial", "guide"])
    .populate(["author", "comments"])
    .select(["title", "content", "author.name"]),
);

// Using Query objects directly
const results = await db.find("users", [
  Query.equal("status", ["active"]),
  Query.greaterThan("age", 18),
  Query.limit(25),
  Query.orderBy("name"),
]);

Indexing

Create indexes for better query performance:

// Key index for faster lookups
await db.createIndex("users", "idx_email", IndexEnum.Key, ["email"]);

// Unique index to enforce uniqueness
await db.createIndex("users", "idx_username", IndexEnum.Unique, ["username"]);

// Fulltext index for search
await db.createIndex("posts", "idx_content", IndexEnum.FullText, [
  "title",
  "content",
]);

// Composite index
await db.createIndex("posts", "idx_author_date", IndexEnum.Key, [
  "author",
  "created_at",
]);

Permissions and Security

Control access with role-based permissions:

// Collection-level permissions
await db.createCollection({
  id: 'posts',
  attributes: [...],
  permissions: [
    Permission.create(Role.user()),
    Permission.read(Role.any()),
    Permission.update(Role.user()),
    Permission.delete(Role.user())
  ],
  documentSecurity: true // Enable document-level permissions
});

// Document-level permissions
await db.createDocument('posts', new Doc({
  title: 'My Post',
  content: 'Content here...',
  $permissions: [
    Permission.read(Role.any()).toString(),
    Permission.update(Role.user('user123')).toString(),
    Permission.delete(Role.user('user123')).toString()
  ]
}));

Advanced Usage

Transactions

Ensure data consistency with transactions:

await db.withTransaction(async () => {
  const user = await db.createDocument("users", userData);
  const profile = await db.createDocument("profiles", {
    userId: user.getId(),
    ...profileData,
  });
  // Both operations succeed or both fail
});

Multi-tenancy

Support multiple tenants in shared infrastructure:

// Configure adapter for shared tables
adapter.setMeta({
  sharedTables: true,
  tenantId: 123,
  namespace: "tenant_app",
});

// All operations will be scoped to the tenant
const users = await db.find("users"); // Only returns tenant 123's users

Caching

Leverage built-in caching for better performance:

// Cache is automatically managed
const user = await db.getDocument("users", "user123"); // Fetches from DB
const userAgain = await db.getDocument("users", "user123"); // Returns from cache

// Manual cache control
await db.purgeCachedDocument("users", "user123");
await db.purgeCachedCollection("users");

Event Handling

Listen to database events:

db.on(EventsEnum.DocumentCreate, (document) => {
  console.log("Document created:", document.getId());
});

db.on(EventsEnum.CollectionCreate, (collection) => {
  console.log("Collection created:", collection.getId());
});

Project Structure

src/
├── adapters/          # Database adapters (PostgreSQL)
│   ├── adapter.ts     # Main adapter implementation
│   ├── base.ts        # Base adapter class
│   ├── postgres.ts    # PostgreSQL client wrapper
│   └── types.ts       # Adapter types
├── core/              # Core database functionality
│   ├── database.ts    # Main Database class
│   ├── doc.ts         # Document class
│   ├── query.ts       # Query building
│   ├── cache.ts       # Caching layer
│   └── enums.ts       # Type enums
├── errors/            # Custom error classes
├── utils/             # Utility functions
│   ├── authorization.ts # Permission handling
│   ├── id.ts          # ID generation
│   ├── permission.ts  # Permission utilities
│   └── query-builder.ts # Query builder
└── validators/        # Data validation
    ├── schema.ts      # Schema validation
    ├── queries/       # Query validation
    └── permissions.ts # Permission validation

Development

Prerequisites

  • Node.js 18.17 or later
  • PostgreSQL 12 or later
  • Bun (recommended) or npm/yarn

Setup

# Clone the repository
git clone https://github.com/Nuvix-Tech/database.git
cd database

# Install dependencies
bun install

# Set up environment variables
cp .env.example .env
# Edit .env with your database configuration

# Run tests
bun test

# Build the library
bun run build

# Type checking
bun run typecheck

# Lint code
bun run lint

Running Tests

# Run all tests
bun test

# Watch mode
bun run test:watch

# Test specific file
bun test tests/database.basic.test.ts

Environment Variables

# PostgreSQL connection string
PG_URL=postgres://postgres:postgres@localhost:5432/test_db

Configuration

Database Adapter Options

const adapter = new Adapter({
  // PostgreSQL connection config
  host: "localhost",
  port: 5432,
  database: "myapp",
  user: "username",
  password: "password",

  // Or use connection string
  connectionString: "postgres://user:pass@host:port/db",

  // Connection pool settings
  max: 20,
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000,
});

// Set metadata for multi-tenancy
adapter.setMeta({
  database: "myapp",
  schema: "public",
  sharedTables: false,
  tenantId: undefined,
  tenantPerDocument: false,
  namespace: "default",
});

Cache Configuration

import { Memory, Redis } from "@nuvix/cache";

// In-memory cache (development)
const cache = new Memory();

// Redis cache (production)
const cache = new Redis({
  host: "localhost",
  port: 6379,
  password: "redis-password",
});

API Reference

Database Class Methods

Collection Management

  • create(database?) - Create database schema
  • createCollection(options) - Create a new collection
  • getCollection(id) - Get collection metadata
  • listCollections(limit?, offset?) - List all collections
  • updateCollection(options) - Update collection permissions
  • deleteCollection(id) - Delete a collection

Document Operations

  • createDocument(collectionId, document) - Create a document
  • createDocuments(collectionId, documents) - Create multiple documents
  • getDocument(collectionId, id, query?) - Get document by ID
  • updateDocument(collectionId, id, updates) - Update a document
  • updateDocuments(collectionId, updates, query?) - Update multiple documents
  • deleteDocument(collectionId, id) - Delete a document
  • deleteDocuments(collectionId, query?) - Delete multiple documents
  • find(collectionId, query?) - Query documents

Attribute Management

  • createAttribute(collectionId, attribute) - Add attribute to collection
  • updateAttribute(collectionId, id, options) - Update attribute properties
  • deleteAttribute(collectionId, id) - Remove attribute from collection
  • renameAttribute(collectionId, oldName, newName) - Rename an attribute

Relationship Management

  • createRelationship(options) - Create relationship between collections
  • updateRelationship(options) - Update existing relationship
  • deleteRelationship(collectionId, id) - Delete a relationship

Index Management

  • createIndex(collectionId, id, type, attributes) - Create an index
  • deleteIndex(collectionId, id) - Delete an index
  • renameIndex(collectionId, oldName, newName) - Rename an index

Contributing

We welcome contributions! Please follow these guidelines:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Write tests for your changes
  4. Ensure all tests pass (bun test)
  5. Commit your changes (git commit -m 'Add amazing feature')
  6. Push to the branch (git push origin feature/amazing-feature)
  7. Open a Pull Request

Code Style

  • Use TypeScript with strict type checking
  • Follow existing code formatting (Prettier)
  • Write comprehensive tests for new features
  • Update documentation for API changes

Testing

All contributions must include tests. We use Vitest for testing:

import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { createTestDb } from "./helpers.js";

describe("Feature", () => {
  const db = createTestDb();

  beforeAll(async () => {
    await db.create();
  });

  afterAll(async () => {
    await db.getAdapter().$client.disconnect();
  });

  it("should work correctly", async () => {
    // Test implementation
    expect(result).toBe(expected);
  });
});

License

This project is licensed under the Proprietary License - see the LICENSE file for details.

Support


Built with ❤️ by the Nuvix team

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors