Subscriptions

Learn how to subscribe to real-time data changes with Query-2jz's subscription system.

What are Subscriptions?

Subscriptions in Query-2jz allow you to receive real-time updates when data changes. They use WebSockets or Server-Sent Events to push updates to your clients automatically.

Subscription Features

Real-time updates
WebSocket & SSE support
Selective field watching
Filtered subscriptions
Automatic reconnection
Connection management

Basic Subscriptions

Subscribe to changes on specific models or records.

Model Subscription

Subscribe to all changes on a model

WebSocket Connection

const ws = new WebSocket('ws://localhost:3000/subscribe');

// Subscribe to User model changes
ws.send(JSON.stringify({
  type: 'subscribe',
  model: 'User',
  action: 'all'  // create, update, delete, or all
}));

Received Updates

// Create event
{
  "type": "create",
  "model": "User",
  "data": {
    "id": "1",
    "name": "John Doe",
    "email": "john@example.com"
  },
  "timestamp": "2024-01-01T00:00:00Z"
}

// Update event
{
  "type": "update",
  "model": "User",
  "data": {
    "id": "1",
    "name": "John Updated",
    "email": "john@example.com"
  },
  "changes": {
    "name": "John Updated"
  },
  "timestamp": "2024-01-01T12:00:00Z"
}

// Delete event
{
  "type": "delete",
  "model": "User",
  "data": {
    "id": "1"
  },
  "timestamp": "2024-01-01T18:00:00Z"
}

Record Subscription

Subscribe to changes on a specific record

WebSocket Connection

// Subscribe to specific user changes
ws.send(JSON.stringify({
  type: 'subscribe',
  model: 'User',
  id: '1',
  action: 'all'
}));

Received Updates

{
  "type": "update",
  "model": "User",
  "id": "1",
  "data": {
    "id": "1",
    "name": "John Updated",
    "email": "john@example.com",
    "lastLoginAt": "2024-01-01T12:00:00Z"
  },
  "changes": {
    "lastLoginAt": "2024-01-01T12:00:00Z"
  },
  "timestamp": "2024-01-01T12:00:00Z"
}

Filtered Subscriptions

Subscribe to changes that match specific criteria.

Conditional Subscriptions

Only receive updates for records matching your criteria

WebSocket Connection

// Subscribe to active users only
ws.send(JSON.stringify({
  type: 'subscribe',
  model: 'User',
  where: {
    "status": "active"
  },
  action: 'all'
}));

// Subscribe to posts by specific author
ws.send(JSON.stringify({
  type: 'subscribe',
  model: 'Post',
  where: {
    "authorId": "1",
    "status": "published"
  },
  action: 'all'
}));

Complex Filters

// Subscribe with complex conditions
ws.send(JSON.stringify({
  type: 'subscribe',
  model: 'User',
  where: {
    "$and": [
      {"status": "active"},
      {"$or": [
        {"role": "admin"},
        {"lastLoginAt": {"$gte": "2024-01-01"}}
      ]}
    ]
  },
  action: 'all'
}));

Field Selection

Choose which fields to include in subscription updates.

Selective Field Updates

Only receive updates for specific fields

WebSocket Connection

// Subscribe to specific fields only
ws.send(JSON.stringify({
  type: 'subscribe',
  model: 'User',
  select: ['id', 'name', 'status', 'lastLoginAt'],
  action: 'all'
}));

Received Updates

{
  "type": "update",
  "model": "User",
  "data": {
    "id": "1",
    "name": "John Doe",
    "status": "active",
    "lastLoginAt": "2024-01-01T12:00:00Z"
  },
  "changes": {
    "lastLoginAt": "2024-01-01T12:00:00Z"
  },
  "timestamp": "2024-01-01T12:00:00Z"
}

Relationship Subscriptions

Subscribe to changes in related models.

Related Model Updates

Get notified when related data changes

WebSocket Connection

// Subscribe to user and their posts
ws.send(JSON.stringify({
  type: 'subscribe',
  model: 'User',
  id: '1',
  include: ['posts'],
  action: 'all'
}));

// Subscribe to posts with author info
ws.send(JSON.stringify({
  type: 'subscribe',
  model: 'Post',
  include: ['author'],
  action: 'all'
}));

Received Updates

{
  "type": "create",
  "model": "Post",
  "data": {
    "id": "2",
    "title": "New Post",
    "content": "This is a new post",
    "authorId": "1",
    "author": {
      "id": "1",
      "name": "John Doe",
      "email": "john@example.com"
    }
  },
  "timestamp": "2024-01-01T12:00:00Z"
}

Connection Management

Handle WebSocket connections, reconnection, and error handling.

WebSocket Client

Complete WebSocket client implementation

class Query-2jzSubscription {
  constructor(url) {
    this.url = url;
    this.ws = null;
    this.subscriptions = new Map();
    this.reconnectAttempts = 0;
    this.maxReconnectAttempts = 5;
    this.reconnectDelay = 1000;
  }

  connect() {
    this.ws = new WebSocket(this.url);
    
    this.ws.onopen = () => {
      console.log('Connected to Query-2jz subscription server');
      this.reconnectAttempts = 0;
      
      // Resubscribe to all previous subscriptions
      this.subscriptions.forEach((config, id) => {
        this.ws.send(JSON.stringify({
          id,
          ...config
        }));
      });
    };

    this.ws.onmessage = (event) => {
      const message = JSON.parse(event.data);
      this.handleMessage(message);
    };

    this.ws.onclose = () => {
      console.log('Connection closed');
      this.reconnect();
    };

    this.ws.onerror = (error) => {
      console.error('WebSocket error:', error);
    };
  }

  subscribe(config) {
    const id = Math.random().toString(36).substr(2, 9);
    this.subscriptions.set(id, config);
    
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify({
        id,
        ...config
      }));
    }
    
    return id;
  }

  unsubscribe(id) {
    this.subscriptions.delete(id);
    
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify({
        type: 'unsubscribe',
        id
      }));
    }
  }

  reconnect() {
    if (this.reconnectAttempts < this.maxReconnectAttempts) {
      this.reconnectAttempts++;
      console.log(`Reconnecting... (attempt ${this.reconnectAttempts})`);
      
      setTimeout(() => {
        this.connect();
      }, this.reconnectDelay * this.reconnectAttempts);
    } else {
      console.error('Max reconnection attempts reached');
    }
  }

  handleMessage(message) {
    // Handle different message types
    switch (message.type) {
      case 'create':
      case 'update':
      case 'delete':
        this.onDataChange(message);
        break;
      case 'error':
        this.onError(message);
        break;
      case 'pong':
        // Heartbeat response
        break;
    }
  }

  onDataChange(message) {
    // Override this method to handle data changes
    console.log('Data changed:', message);
  }

  onError(message) {
    console.error('Subscription error:', message);
  }

  disconnect() {
    if (this.ws) {
      this.ws.close();
    }
  }
}

// Usage
const subscription = new Query-2jzSubscription('ws://localhost:3000/subscribe');

subscription.onDataChange = (message) => {
  // Handle real-time updates
  if (message.model === 'User') {
    updateUserList(message);
  }
};

subscription.connect();

// Subscribe to user changes
const userSubId = subscription.subscribe({
  type: 'subscribe',
  model: 'User',
  action: 'all'
});

Server-Sent Events

Alternative to WebSockets for one-way communication.

SSE Implementation

Use Server-Sent Events for subscriptions

Client Implementation

// Create SSE connection
const eventSource = new EventSource('/api/query-2jz/subscribe?model=User&action=all');

eventSource.onmessage = (event) => {
  const message = JSON.parse(event.data);
  handleSubscriptionUpdate(message);
};

eventSource.onerror = (error) => {
  console.error('SSE error:', error);
  // SSE automatically reconnects
};

eventSource.onopen = () => {
  console.log('SSE connection opened');
};

// Close connection
eventSource.close();

URL Parameters

// Basic subscription
/api/query-2jz/subscribe?model=User&action=all

// Filtered subscription
/api/query-2jz/subscribe?model=User&where={"status":"active"}&action=all

// Field selection
/api/query-2jz/subscribe?model=User&select=id,name,status&action=all

// Specific record
/api/query-2jz/subscribe?model=User&id=1&action=all

Best Practices

Do

  • • Use filtered subscriptions to reduce noise
  • • Select only needed fields
  • • Implement proper error handling
  • • Handle reconnection gracefully
  • • Clean up subscriptions when done
  • • Use heartbeats for connection health
  • • Batch updates when possible

Don't

  • • Subscribe to everything without filters
  • • Ignore connection errors
  • • Forget to unsubscribe
  • • Process every update immediately
  • • Use subscriptions for one-time data
  • • Ignore memory leaks
  • • Skip error recovery