@dataql/node
Version:
DataQL core SDK for unified data management with MongoDB and GraphQL - Production Multi-Cloud Ready
1,308 lines (1,041 loc) • 35.3 kB
Markdown
# DataQL Node SDK
## Installation
```sh
npm install /node
```
## Quick Start
```ts
import { Data } from "@dataql/node";
const data = new Data({
appToken: "app_sometoken",
});
// Data operations
const users = data.collection("users", userSchema);
await users.create({ name: "Alice", email: "alice@example.com" });
```
## Features
- **🔌 Plugin Ecosystem**: Comprehensive plugin system for extending DataQL functionality
- **🔍 Database Introspection**: Automatically analyze existing databases and generate DataQL schemas
- **☁️ One-Click Cloud Migration**: Migrate entire databases to DataQL cloud infrastructure instantly
- **📱 Offline-First Architecture**: Work offline with automatic sync when reconnected
- **⚡ Real-Time Synchronization**: Live updates across all connected clients
- **🔄 Zero-API-Change Migration**: Drop-in replacement for existing ORMs and databases
- **🚀 Auto-Scaling Infrastructure**: Handles traffic spikes automatically
- **🗄️ Multi-Database Support**: MongoDB with expanding support for PostgreSQL, MySQL, SQLite
- **🔒 Type-Safe Operations**: Full TypeScript support with compile-time validation
- **🎯 Document-Scoped API**: Intuitive subdocument operations without MongoDB operators
## 🔌 Plugin Ecosystem
**NEW**: DataQL now features a revolutionary plugin system that transforms it into a extensible platform. Build database adapters, middleware, integrations, and extensions to create a powerful ecosystem around your data layer.
### Plugin Types
- **🔗 Database Adapters**: Support new database types (PostgreSQL, MySQL, SQLite, etc.)
- **⚙️ Middleware**: Process requests and responses (auth, rate limiting, logging)
- **🔧 Extensions**: Add new methods and functionality to DataQL
- **🪝 Hooks**: React to DataQL events and lifecycle
- **🔌 Integrations**: Connect with external services (analytics, notifications, etc.)
- **🎨 UI Components**: Build dashboards and management interfaces
### Quick Start
```ts
import { Data, Plugin } from "@dataql/node";
// Create a simple analytics plugin
class AnalyticsPlugin implements Plugin {
id = "analytics";
name = "Analytics Plugin";
version = "1.0.0";
type = "extension" as const;
methods = {
async trackEvent(eventName: string, properties?: any) {
console.log(`Event: ${eventName}`, properties);
// Send to analytics service
},
};
async initialize(context) {
context.logger.info("Analytics plugin initialized");
}
}
// Register and use plugins
const data = new Data({ appToken: "your-token" });
await data.registerPlugin(new AnalyticsPlugin());
await data.initializePlugins();
await data.applyExtensions();
// Use extended functionality (added by plugin)
await (data as any).trackEvent("user_signup", { userId: "123" });
```
### Database Adapter Example
```ts
import { DatabaseAdapterPlugin } from "@dataql/node";
class PostgreSQLAdapter implements DatabaseAdapterPlugin {
id = "postgresql-adapter";
name = "PostgreSQL Adapter";
version = "1.0.0";
type = "adapter" as const;
databaseType = "postgresql";
async initialize(context) {
// Setup PostgreSQL connection pool
}
async createConnection(connectionString: string) {
// Create PostgreSQL connection
}
async introspect(connection, options) {
// Analyze PostgreSQL database structure
}
async executeQuery(connection, query) {
// Execute SQL queries
}
}
// Register PostgreSQL support
await data.registerPlugin(new PostgreSQLAdapter(), {
connectionString: "postgresql://user:pass@localhost:5432/db",
});
```
### Middleware Example
```ts
import { MiddlewarePlugin } from "@dataql/node";
class AuthMiddleware implements MiddlewarePlugin {
id = "auth-middleware";
name = "Authentication Middleware";
type = "middleware" as const;
order = 1; // Execute first
async processRequest(request) {
const token = request.headers.authorization;
if (!token) throw new Error("Authentication required");
// Verify JWT and add user to request
request.user = await this.verifyToken(token);
return request;
}
}
```
### Integration Example
```ts
import { IntegrationPlugin } from "@dataql/node";
class SlackIntegration implements IntegrationPlugin {
id = "slack-integration";
name = "Slack Integration";
type = "integration" as const;
service = "slack";
async syncTo(data, options) {
// Send notification to Slack
await this.sendToSlack({
text: `DataQL Alert: ${data.message}`,
channel: options?.channel || "#general",
});
}
}
```
### Hook System
React to DataQL events automatically:
```ts
import { HookPlugin } from "@dataql/node";
class CacheInvalidationHook implements HookPlugin {
id = "cache-invalidation";
type = "hook" as const;
hooks = {
afterCreate: async (data, context) => {
await this.invalidateCache(data.collection);
},
afterUpdate: async (data, context) => {
await this.invalidateCache(data.collection);
},
};
}
```
### Plugin Management
```ts
// Get plugin information
const plugins = data.getPlugins();
console.log(
"Registered plugins:",
plugins.map((p) => p.name)
);
// Plugin statistics
const stats = data.getPluginStats();
console.log("Plugin stats:", stats);
// Check for specific plugins
if (data.hasPlugin("postgres-adapter")) {
console.log("PostgreSQL support available");
}
// Get plugins by type
const adapters = data.getPluginsByType("adapter");
const middleware = data.getPluginsByType("middleware");
```
### Plugin Discovery & Registry
```ts
import { PluginRegistry } from "@dataql/node";
const registry = new PluginRegistry();
// Search for plugins
const plugins = await registry.search("postgresql", "adapter");
// Install plugins
await registry.install("@yourorg/dataql-postgres-adapter");
// Publish your plugins
await registry.publish(myPlugin, manifest);
```
### Available Hooks
DataQL provides comprehensive hooks for plugin integration:
- **Request/Response**: `beforeRequest`, `afterRequest`
- **CRUD Operations**: `beforeCreate`, `afterCreate`, `beforeRead`, `afterRead`, `beforeUpdate`, `afterUpdate`, `beforeDelete`, `afterDelete`
- **Transactions**: `beforeTransaction`, `afterTransaction`
- **Database**: `beforeIntrospection`, `afterIntrospection`, `beforeMigration`, `afterMigration`
- **Lifecycle**: `onConnect`, `onDisconnect`, `onError`
- **Schema**: `onSchemaChange`, `onDataChange`
### Plugin Development
Create powerful plugins with full TypeScript support:
```ts
import { Plugin, PluginContext } from "@dataql/node";
export class MyPlugin implements Plugin {
id = "my-plugin";
name = "My Plugin";
version = "1.0.0";
description = "Custom plugin description";
author = "Your Name";
type = "extension" as const;
configSchema = {
apiKey: { type: "string", required: true },
endpoint: { type: "string", required: false },
};
async initialize(context: PluginContext) {
const { config, logger, events, utils } = context;
// Setup plugin functionality
logger.info("Plugin initialized");
// Listen to events
events.on("afterCreate", (data) => {
// React to data creation
});
}
async destroy() {
// Cleanup resources
}
}
```
### Plugin Package Structure
```json
{
"name": "@yourorg/dataql-my-plugin",
"version": "1.0.0",
"dataql": {
"plugin": {
"type": "extension",
"entry": "./dist/index.js",
"name": "My Plugin",
"config": {
"apiKey": { "type": "string", "required": true }
}
}
},
"peerDependencies": {
"@dataql/node": "^0.7.0"
}
}
```
See the complete [Plugin Development Guide](./PLUGIN_DEVELOPMENT_GUIDE.md) for detailed documentation and examples.
## 🔍 Database Introspection
**NEW**: DataQL can automatically analyze your existing database and generate schemas, allowing you to migrate without changing your data structure. The introspection follows DataQL's core architecture: **SDK → Worker → Lambda → Database** (no direct database connections from SDK).
```ts
import { Data } from "@dataql/node";
const data = new Data({
appToken: "your-app-token",
env: "dev",
});
// Introspect an existing MongoDB database
const result = await data.introspect("mongodb://localhost:27017/ecommerce", {
databaseName: "ecommerce",
sampleSize: 100,
includeIndexes: true,
excludeCollections: ["logs", "temp"],
});
if (result.success) {
console.log(`Found ${Object.keys(result.schemas).length} collections`);
// Schemas are automatically registered - use them immediately!
const users = data.collection("users", result.schemas.users);
const products = data.collection("products", result.schemas.products);
// All DataQL features now available on your existing data
const allUsers = await users.find();
await products.create({ name: "New Product", price: 99.99 });
}
```
### Introspection Benefits
- 🚀 **Zero-API-change migration**: Keep your existing database, gain DataQL benefits
- 🤖 **Smart type inference**: Automatically detects field types, formats, and constraints
- 📊 **Index preservation**: Maintains your existing database indexes and relationships
- 🔄 **Multi-database support**: MongoDB (available now), PostgreSQL, MySQL coming soon
- ⚡ **Instant migration**: From analysis to working DataQL code in seconds
### Advanced Introspection
```ts
// Fine-tune introspection for large databases
const result = await data.introspect(databaseUrl, {
sampleSize: 50, // Documents to sample per collection
maxDepth: 3, // Nested object analysis depth
includeIndexes: true, // Analyze database indexes
excludeCollections: ["logs", "analytics"], // Skip large collections
includeCollections: ["users", "orders"], // Only analyze specific collections
});
// Use directly without Data instance
import { IntrospectionService } from "@dataql/node";
const result = await IntrospectionService.introspect(
{
url: "mongodb://localhost:27017/blog",
name: "blog",
},
{
sampleSize: 100,
includeIndexes: true,
}
);
```
See the [full introspection guide](./INTROSPECTION_GUIDE.md) for complete documentation and examples.
## ☁️ One-Click Cloud Migration
**REVOLUTIONARY**: Migrate your entire database to DataQL's cloud infrastructure in **ONE LINE OF CODE**! Get instant access to offline-first capabilities, real-time sync, and auto-scaling.
```ts
// Migrate your entire database to DataQL cloud in one line!
const result = await data.migrateToCloud("mongodb://localhost:27017/ecommerce");
if (result.success) {
console.log(`✅ Migration completed!`);
console.log(`☁️ Cloud database: ${result.cloudDatabase.name}`);
console.log(`🔗 New connection: ${result.cloudDatabase.connectionString}`);
console.log(
`📊 Migrated: ${result.migration.collections} collections, ${result.migration.documents} documents`
);
// Your data is now available with DataQL superpowers!
// Schemas are auto-registered and ready to use immediately
const users = data.collection("users", result.schemas.users);
const products = data.collection("products", result.schemas.products);
// Everything works exactly the same, but now with:
// ✅ Offline-first capabilities
// ✅ Real-time synchronization
// ✅ Auto-scaling infrastructure
// ✅ Built-in caching and optimization
}
```
### Migration Features
- **One-Click Migration**: Entire database migration in a single method call
- **Zero Downtime**: Migrate without interrupting your application
- **Data Validation**: Automatic integrity checks during migration
- **Rollback Support**: 24-hour rollback window for peace of mind
- **Instant Benefits**: Immediate access to DataQL's powerful features
- **Smart Optimization**: Automatic performance tuning for your data patterns
### Advanced Migration Options
```ts
const result = await data.migrateToCloud(
"mongodb://localhost:27017/ecommerce",
{
// Introspection options
sampleSize: 500,
includeIndexes: true,
excludeCollections: ["temp_logs", "cache_data"],
// Migration options
batchSize: 2000,
validateData: true,
preserveIds: true,
createBackup: true,
}
);
// Real-world production migration
const prodResult = await data.migrateToCloud(
"mongodb+srv://user:pass@cluster.mongodb.net/production",
{
excludeCollections: ["sessions", "temp_data", "audit_logs"],
batchSize: 1000,
validateData: true,
preserveIds: true,
}
);
if (prodResult.success) {
console.log("🎉 Production migration successful!");
console.log(`📊 Migrated ${prodResult.migration.collections} collections`);
console.log(`⏱️ Duration: ${prodResult.migration.duration}ms`);
// Update your environment variables:
console.log(`DATABASE_URL=${prodResult.cloudDatabase.connectionString}`);
console.log(`MIGRATION_ID=${prodResult.migrationId}`);
}
```
### Why Migrate to DataQL Cloud?
- 🚀 **Instant Performance**: Auto-scaling infrastructure handles any load
- 📱 **Offline-First**: Apps work perfectly without internet connection
- 🔄 **Real-Time Sync**: Live updates across all connected clients
- 🔒 **Built-in Security**: Enterprise-grade security and compliance
- 🌍 **Global CDN**: Fast access from anywhere in the world
- 🧠 **Smart Caching**: Intelligent caching reduces database load
- 🔧 **Zero Maintenance**: No server management or scaling concerns
### Example: E-commerce Migration
```ts
// Real-world e-commerce platform migration
const result = await data.migrateToCloud(
"mongodb+srv://admin:pass@cluster.mongodb.net/ecommerce_prod",
{
excludeCollections: ["user_sessions", "audit_logs", "temp_cart_items"],
batchSize: 2000,
includeIndexes: true,
validateData: true,
preserveIds: true,
}
);
if (result.success) {
// Your e-commerce platform now has:
// ✅ Offline-first shopping experience
// ✅ Real-time inventory synchronization
// ✅ Auto-scaling for Black Friday traffic
// ✅ Global CDN for worldwide customers
// ✅ Built-in conflict resolution for concurrent orders
// Immediate usage of migrated data
const products = data.collection("products", result.schemas.products);
const orders = data.collection("orders", result.schemas.orders);
// Create a new product to test the migrated system
await products.create({
name: "DataQL Migration Success T-Shirt",
price: 29.99,
category: "Apparel",
description: "Celebrate your successful database migration!",
});
}
```
For comprehensive migration examples, see `examples/cloud-migration.ts`.
## Example: Document-Scoped Subdocument Operations
```ts
const users = data.collection("users", userSchema);
// Create a user
const newUser = await users.create({
name: "Alice",
email: "alice@example.com",
});
// Document-scoped subdocument operations
const user = users({ id: newUser.insertedId });
// Work with subdocuments
await user.addresses.create({
street: "123 Main St",
city: "New York",
zipCode: "10001",
});
await user.addresses.update({ city: "New York" }, { zipCode: "10002" });
const nyAddresses = await user.addresses.find({ city: "New York" });
await user.preferences.update({
theme: "dark",
notifications: true,
});
```
For more examples, see `examples/document-scoped-usage.ts`.
## Unique Document Creation
DataQL provides `createUnique()` method for both collections and subcollections to prevent duplicate documents based on comparable fields (excluding ID and subdocument fields).
### Collection-level createUnique
```ts
const users = data.collection("users", userSchema);
// Create a unique user (will create new document)
const result1 = await users.createUnique({
name: "John Doe",
email: "john@example.com",
age: 30,
preferences: {
theme: "dark",
notifications: true,
},
});
console.log(result1.isExisting); // false
console.log(result1.insertedId); // "uuid-123"
// Try to create the same user again (will return existing)
// Comparison excludes 'id', 'createdAt', 'updatedAt', and subdocuments like 'preferences'
const result2 = await users.createUnique({
name: "John Doe", // Same
email: "john@example.com", // Same
age: 30, // Same
preferences: {
theme: "light", // Different but ignored (subdocument)
notifications: false,
},
});
console.log(result2.isExisting); // true
console.log(result2.insertedId); // "uuid-123" (same as result1)
```
### Subcollection createUnique
```ts
const user = users({ id: userId });
// Create unique address
const address1 = await user.addresses.createUnique({
street: "123 Main St",
city: "New York",
zipCode: "10001",
country: "USA",
});
console.log(address1.isExisting); // false
// Try to create the same address (will return existing)
const address2 = await user.addresses.createUnique({
street: "123 Main St",
city: "New York",
zipCode: "10001",
country: "USA",
});
console.log(address2.isExisting); // true
```
### Excluded from Comparison
The `createUnique` method excludes these fields from uniqueness comparison:
1. **ID fields**: `id`, `_id`, and any field containing 'id'
2. **Auto-generated timestamps**: `createdAt`, `updatedAt`
3. **Subdocument objects**: Nested objects like `preferences`, `profile`
4. **Subdocument arrays**: Arrays of objects like `addresses`, `reviews`
### Compared Fields
Only these fields are compared for uniqueness:
1. **Primitive fields**: strings, numbers, booleans
2. **Enum fields**: category selections
3. **Simple arrays**: arrays of primitive values (if any)
### Return Value
```ts
type CreateUniqueResult = {
insertedId?: string; // ID of document (new or existing)
result?: any; // Full document data
isExisting?: boolean; // true if existed, false if newly created
};
```
For complete examples, see `examples/unique-document-creation.ts`.
## Collection References
DataQL supports defining references between collections using the `Ref()` function, enabling relational data modeling with type safety.
### Quick Summary
To define references between collections in DataQL:
1. **Import `Ref`**: `import { Ref } from "@dataql/node"`
2. **Define reference fields**: `ownerId: Ref(userSchema)` or `assigneeId: Ref("User")`
3. **Arrays of references**: `memberIds: [Ref(userSchema)]`
4. **Use in operations**: Store actual IDs as values: `{ ownerId: "user-123" }`
### Defining References
```ts
import { Data, ID, String, Ref } from "@dataql/node";
// Define schemas
const userSchema = {
id: ID,
name: String,
email: String,
} as const;
const projectSchema = {
id: ID,
name: String,
description: String,
ownerId: Ref(userSchema), // Reference to user schema
teamMemberIds: [Ref(userSchema)], // Array of user references
} as const;
const taskSchema = {
id: ID,
title: String,
completed: Boolean,
userId: Ref(userSchema), // Reference to user
projectId: Ref(projectSchema), // Reference to project
assigneeId: Ref("User"), // Reference by string name
} as const;
```
### Reference Syntax Options
```ts
// 1. Schema Object Reference (Recommended)
ownerId: Ref(userSchema), // Direct schema reference
// 2. String Reference
assigneeId: Ref("User"), // Reference by collection name
// 3. Array of References
memberIds: [Ref(userSchema)], // Array of user references
tagIds: [Ref("Tag")], // Array of string references
// 4. Nested Reference Objects
participants: [
{
userId: Ref(userSchema),
role: ["admin", "member", "viewer"],
joinedAt: "Timestamp",
}
],
```
### Working with Referenced Data
```ts
const data = new Data({ appToken: "your-token" });
const users = data.collection("users", userSchema);
const projects = data.collection("projects", projectSchema);
const tasks = data.collection("tasks", taskSchema);
// Create referenced documents
const user = await users.create({
name: "Alice Johnson",
email: "alice@example.com",
});
const project = await projects.create({
name: "Website Redesign",
description: "Complete redesign of company website",
ownerId: user.insertedId, // Reference the user's ID
teamMemberIds: [user.insertedId], // Array of user IDs
});
const task = await tasks.create({
title: "Design homepage mockup",
completed: false,
userId: user.insertedId, // Reference to user
projectId: project.insertedId, // Reference to project
});
```
### Cross-Collection Queries
```ts
// Find tasks for a specific user
const userTasks = await tasks.find({ userId: user.insertedId });
// Find projects owned by a user
const userProjects = await projects.find({ ownerId: user.insertedId });
// Find all team members for a project
const project = await projects.find({ id: projectId });
const teamMembers = await users.find({
id: { $in: project[0].teamMemberIds },
});
```
### Reference Validation
DataQL automatically validates that referenced IDs exist when creating or updating documents:
```ts
// This will succeed if the user exists
await tasks.create({
title: "New task",
userId: "existing-user-id",
projectId: "existing-project-id",
});
// This will fail with a validation error
await tasks.create({
title: "Invalid task",
userId: "non-existent-user-id", // Validation error
});
```
### Document-Scoped Operations with References
```ts
// Work with referenced data using document-scoped API
const project = projects({ id: projectId });
// Add team members to project
await project.update({
teamMemberIds: [...existingMemberIds, newUserId],
});
// Find tasks for this project
const projectTasks = await tasks.find({ projectId: projectId });
// Update task assignments
const task = tasks({ id: taskId });
await task.update({ userId: newAssigneeId });
```
### Advanced Reference Patterns
```ts
// Self-referencing (e.g., user management hierarchy)
const userSchema = {
id: ID,
name: String,
managerId: Ref("User"), // Self-reference
directReports: [Ref("User")], // Array of self-references
} as const;
// Polymorphic references (reference to multiple collection types)
const commentSchema = {
id: ID,
content: String,
authorId: Ref(userSchema),
// Reference different entity types
entityType: ["task", "project", "user"],
entityId: String, // ID of the referenced entity
} as const;
// Many-to-many relationships through junction collections
const userProjectSchema = {
id: ID,
userId: Ref(userSchema),
projectId: Ref(projectSchema),
role: ["owner", "member", "viewer"],
joinedAt: "Timestamp",
} as const;
```
### Reference Benefits
- **Type Safety**: Full TypeScript inference for referenced fields
- **Validation**: Automatic validation of reference integrity
- **Performance**: Optimized queries for related data
- **Consistency**: Maintains data relationships across collections
- **Flexibility**: Support for various reference patterns and relationships
For complete examples, see `examples/simple-references.ts` and `examples/collection-references.ts`.
### Schema Composition Benefits
Schema composition provides several advantages:
- **Reusability**: Define common structures once, use everywhere
- **Maintainability**: Update schema in one place, changes propagate
- **Type Safety**: Full TypeScript inference across composed schemas
- **Consistency**: Ensure uniform data structures across collections
- **Modularity**: Build complex schemas from simple building blocks
### Using Composed Schemas
```ts
// Create collections using composed schemas
const users = data.collection("users", userSchema);
const accounts = data.collection("accounts", accountSchema);
const organizations = data.collection("organizations", organizationSchema);
// Create documents with composed subdocuments
await accounts.create({
accountName: "Enterprise Account",
users: [
{ name: "John Doe", email: "john@example.com" },
{ name: "Jane Smith", email: "jane@example.com" },
],
billingAddress: {
street: "123 Business Ave",
city: "Enterprise City",
zipCode: "12345",
country: "USA",
},
shippingAddresses: [
{
street: "456 Shipping St",
city: "Warehouse City",
zipCode: "67890",
country: "USA",
},
],
});
```
## Working with Subdocuments
### Creating Documents with Subdocuments
```ts
const users = data.collection("users", userSchema);
const newUser = await users.create({
name: "John Doe",
email: "john@example.com",
preferences: {
theme: "dark",
notifications: true,
language: "en",
},
addresses: [
{
street: "123 Main St",
city: "New York",
zipCode: "10001",
country: "USA",
},
{
street: "456 Oak Ave",
city: "San Francisco",
zipCode: "94102",
country: "USA",
},
],
profile: {
bio: "Software developer",
social: {
twitter: "@johndoe",
github: "johndoe",
},
work: {
company: "Tech Corp",
position: "Senior Developer",
address: {
street: "789 Tech Blvd",
city: "Silicon Valley",
country: "USA",
},
},
},
});
```
### Working with Composed Subdocuments
```ts
const accounts = data.collection("accounts", accountSchema);
// Document-scoped operations on composed subdocuments
const account = accounts({ id: accountId });
// Add new user to account
await account.users.create({
name: "New User",
email: "newuser@example.com",
});
// Update billing address (composed addressSchema)
await account.billingAddress.update({
street: "Updated Street",
city: "New City",
});
// Add new shipping address
await account.shippingAddresses.create({
street: "Additional Shipping St",
city: "Shipping City",
zipCode: "99999",
country: "USA",
});
```
### Querying Subdocuments
```ts
// Find users by nested field
const users = await users.find({
"preferences.theme": "dark",
});
// Find users by deeply nested field
const techUsers = await users.find({
"profile.work.company": "Tech Corp",
});
// Find users by array subdocument field
const nyUsers = await users.find({
"addresses.city": "New York",
});
```
### Updating Subdocuments
```ts
const users = data.collection("users", userSchema);
const user = users({ id: userId });
// Update nested object fields directly
await user.preferences.update({
theme: "light",
});
await user.profile.update({
bio: "Senior Software Developer",
});
// Update entire nested object
await user.preferences.update({
theme: "light",
notifications: false,
language: "es",
});
// Add to array of subdocuments (document-scoped API)
await user.addresses.create({
street: "789 New St",
city: "Boston",
zipCode: "02101",
country: "USA",
});
// Update specific subdocuments in array
await user.addresses.update({ city: "Boston" }, { zipCode: "02102" });
```
### Finding and Updating Subdocuments by Field
DataQL provides powerful document-scoped operations for working with subdocuments in an intuitive way.
#### Document-Scoped Subdocument API
```ts
const users = data.collection("users", userSchema);
// Document-scoped subdocument operations
const user = users({ id: userId });
// Create new subdocuments
await user.addresses.create({
street: "123 Boston St",
city: "Boston",
zipCode: "02101",
country: "USA",
});
// Find specific addresses within a user's document
const bostonAddresses = await user.addresses.find({ city: "Boston" });
// Update specific subdocuments by field
await user.addresses.update(
{ city: "Boston" }, // Find criteria
{ zipCode: "02102" } // Update data
);
// Delete specific subdocuments
await user.addresses.delete({ city: "Boston" });
// Batch operations on subdocuments
await user.addresses.updateMany({ country: "USA" }, { isActive: true });
await user.addresses.deleteMany({ isActive: false });
// Nested subdocument updates
await user.profile.update({ bio: "Updated bio text" });
// Variable-based approach for multiple operations
const userAddresses = user.addresses;
await userAddresses.create({ street: "456 Main St", city: "NYC" });
await userAddresses.update({ city: "NYC" }, { zipCode: "10001" });
const nycAddresses = await userAddresses.find({ city: "NYC" });
```
#### Benefits of Document-Scoped API
- **More Intuitive**: Natural object-oriented operations instead of MongoDB operators
- **Type Safe**: Full TypeScript support for subdocument fields
- **Document Context**: Clear parent-child relationship with automatic scoping
- **Cleaner Syntax**: No need for `$push`, `$pull`, `$` positional operators
- **Variable References**: Store document/subdocument instances for cleaner multi-operation code
## Schema Definition
### Basic Schema
```ts
const userSchema = {
id: "ID",
name: "String",
email: "String",
age: "Int",
isActive: "Boolean",
createdAt: "Date",
} as const;
```
### Auto-Generated Fields
DataQL automatically adds essential metadata fields to all schemas if they're not explicitly defined:
```ts
// You define this minimal schema
const userSchema = {
name: "String",
email: "String",
} as const;
// DataQL automatically expands it to include:
// {
// id: "ID", // Auto-added if not present
// name: "String",
// email: "String",
// createdAt: "Timestamp", // Auto-added if not present
// updatedAt: "Timestamp", // Auto-added if not present
// }
```
**Auto-generated fields:**
- **`id`**: Unique identifier (UUID v4 string) - added if not specified
- **`createdAt`**: Creation timestamp - added if not specified
- **`updatedAt`**: Last modification timestamp - added if not specified
This ensures all documents have consistent metadata without requiring boilerplate in every schema definition.
**When to explicitly define these fields:**
- **Custom ID format**: If you need a specific ID format (e.g., `orderId: "String"` for custom order numbers)
- **Different timestamp types**: If you prefer `Date` over `Timestamp` for certain use cases
- **Field constraints**: If you need validation rules or default values for these fields
```ts
// Explicit definition example
const customSchema = {
orderId: "String", // Custom ID instead of auto-generated UUID
name: "String",
email: "String",
createdAt: "Date", // Use Date instead of Timestamp
// updatedAt will still be auto-added as Timestamp
} as const;
```
### Enum Field Definitions
DataQL supports multiple syntaxes for defining enum fields:
```ts
const userSchema = {
name: "String",
// Simple array syntax (recommended for clean schemas)
role: ["admin", "user", "guest"],
status: ["active", "inactive", "pending"],
// Explicit enum object syntax (for additional options)
priority: { enum: ["low", "medium", "high"] },
theme: { enum: ["light", "dark"], required: true },
// Mixed usage is perfectly valid
department: ["engineering", "sales", "marketing"],
level: { enum: ["junior", "mid", "senior"], required: true },
} as const;
```
**Benefits of simplified enum syntax:**
- **Cleaner code**: `["one", "two", "three"]` vs `{ enum: ["one", "two", "three"] }`
- **Less verbose**: Reduces boilerplate for simple enums
- **TypeScript friendly**: Full type inference and autocompletion
- **Backward compatible**: Existing `{ enum: [...] }` syntax still works
### Subdocuments (Nested Objects)
DataQL supports multiple ways to define subdocuments and nested data structures:
#### 1. Simple Nested Objects
```ts
const userSchema = {
id: "ID",
name: "String",
email: "String",
preferences: {
theme: "String",
notifications: "Boolean",
language: "String",
},
} as const;
```
#### 2. Arrays of Subdocuments
```ts
const userSchema = {
id: "ID",
name: "String",
addresses: [
{
street: "String",
city: "String",
zipCode: "String",
country: "String",
},
],
} as const;
```
#### 3. Deep Nested Structures
```ts
const userSchema = {
id: "ID",
name: "String",
profile: {
bio: "String",
social: {
twitter: "String",
github: "String",
linkedin: "String",
},
work: {
company: "String",
position: "String",
address: {
street: "String",
city: "String",
country: "String",
},
},
},
} as const;
```
#### 4. Schema Composition and Reusability
```ts
// Define reusable schemas (IDs auto-added by DataQL)
const addressSchema = {
street: "String",
city: "String",
zipCode: "String",
country: "String",
} as const;
const userSchema = {
name: "String",
email: "String",
} as const;
// Compose schemas using references (IDs auto-added)
const accountSchema = {
accountName: "String",
users: [userSchema], // Array of user subdocuments
billingAddress: addressSchema, // Single address subdocument
shippingAddresses: [addressSchema], // Array of address subdocuments
} as const;
const organizationSchema = {
name: "String",
accounts: [accountSchema], // Nested composition
headquarters: addressSchema,
employees: [userSchema],
} as const;
```
#### 5. Simplified Enum Syntax
```ts
// DataQL supports multiple enum syntaxes
const productSchema = {
name: "String",
category: ["electronics", "clothing", "books"], // Array shorthand
status: { enum: ["active", "inactive"] }, // Explicit enum object
priority: { enum: ["low", "medium", "high"], required: true }, // With options
} as const;
```
#### 6. Mixed Complex Schema
```ts
const orderSchema = {
customerId: "String",
status: ["pending", "completed", "cancelled"], // Simplified enum syntax
items: [
{
productId: "String",
name: "String",
quantity: "Int",
price: "Decimal",
metadata: {
category: "String",
tags: ["String"],
},
},
],
shipping: {
address: {
street: "String",
city: "String",
zipCode: "String",
},
method: "String",
trackingNumber: "String",
},
payment: {
method: "String",
amount: "Decimal",
currency: "String",
processor: {
name: "String",
transactionId: "String",
metadata: "Object", // For flexible JSON data
},
},
createdAt: "Date",
updatedAt: "Date",
} as const;
```
## Advanced Usage
- You can use `Data` and `BaseDataQLClient` classes directly for advanced scenarios.
- Supports transactions, bulk operations, and complex queries.
- Compatible with existing database adapters (Drizzle, Prisma, Mongoose, etc.).
## Dual ESM + CommonJS Support
DataQL Node SDK supports both ESM and CommonJS out of the box (Node.js 16+):
- **ESM Usage:**
```js
import { Data } from "@dataql/node";
const data = new Data({ appToken: "your-token" });
```
- **CommonJS Usage:**
```js
const { Data } = require("@dataql/node");
const data = new Data({ appToken: "your-token" });
```
The SDK uses the `exports` field in `package.json` to provide the correct entrypoint for both module types. No extra configuration is needed.
- ESM entry: `./dist/esm/index.js`
- CJS entry: `./dist/cjs/index.js`
**Note:** Do not add `type: "module"` to your own `package.json` unless you want your project to be ESM-only.
> **Note:** The SDK no longer accepts a `workerUrl` parameter. All requests are routed through the built-in DataQL worker URL (`https://edge.dataql.com`).
> If you need to override routing for advanced use cases, use the `customConnection` option.