Skip to Content
AdvancedMiddleware Hooks

Middleware & Lifecycle Hooks

Hook into Gluonic’s sync lifecycle for logging, analytics, transformations, and custom logic.

Overview

Middleware lets you intercept sync operations at various lifecycle points:

  • Before/after mutations
  • Before/after bootstrap
  • Before/after delta sync
  • On WebSocket connect/disconnect
  • On conflicts
  • On errors

Client Middleware

Basic Setup

import { createSyncClient } from '@gluonic/client' const { store } = createSyncClient({ middleware: { beforeMutation: async (mutation) => { console.log('Sending mutation:', mutation) // Return mutation (or modified version) return mutation }, afterMutation: async (mutation, result) => { if (result.ok) { console.log('Mutation succeeded:', mutation.type) } else { console.error('Mutation failed:', result.error) } }, beforeBootstrap: async () => { console.log('Starting bootstrap...') }, afterBootstrap: async (rows) => { console.log(`Bootstrap complete: ${rows.length} rows`) }, onFrame: async (frame) => { console.log('Received frame:', frame) }, onError: async (error, context) => { console.error('Sync error:', error, context) } } })

Analytics Tracking

import { analytics } from './analytics' middleware: { afterMutation: async (mutation, result) => { analytics.track('mutation', { model: mutation.type, operation: mutation.op, success: result.ok, error: result.error }) }, afterBootstrap: async (rows) => { analytics.track('bootstrap_complete', { rowCount: rows.length, duration: performance.now() - startTime }) } }

Error Reporting

import * as Sentry from '@sentry/react-native' middleware: { onError: async (error, context) => { Sentry.captureException(error, { tags: { syncOperation: context.operation, model: context.model } }) }, afterMutation: async (mutation, result) => { if (!result.ok) { Sentry.captureMessage('Mutation failed', { level: 'warning', extra: { mutation, result } }) } } }

Data Transformation

middleware: { beforeMutation: async (mutation) => { // Transform data before sending if (mutation.type === 'post') { const patch = { ...mutation.patch } // Sanitize HTML if (patch.content) { patch.content = sanitizeHTML(patch.content) } // Add metadata patch._clientVersion = appVersion patch._platform = Platform.OS return { ...mutation, patch } } return mutation } }

Rate Limiting

middleware: { beforeMutation: async (mutation) => { const count = await rateLimiter.increment(mutation.type) if (count > 100) { throw new Error('Rate limit exceeded') } return mutation } }

Server Middleware

Request Logging

await SyncServer(app, { adapter, auth, middleware: { beforeRequest: async (req, operation) => { console.log(`[${operation}] ${req.user?.id}`) }, afterRequest: async (req, operation, duration) => { console.log(`[${operation}] completed in ${duration}ms`) } } })

Mutation Auditing

middleware: { afterMutation: async (mutation, result, context) => { // Log all data changes for compliance await audit.log({ timestamp: Date.now(), userId: context.userId, orgId: context.orgId, model: mutation.type, modelId: mutation.id, operation: mutation.op, patch: mutation.patch, success: result.ok, ip: context.req.ip }) } }

Performance Monitoring

middleware: { beforeBootstrap: async (orgId) => { return { startTime: Date.now(), orgId } }, afterBootstrap: async (orgId, rows, context) => { const duration = Date.now() - context.startTime await metrics.record('bootstrap_duration', duration, { orgId, rowCount: rows.length }) } }

Lifecycle Events

Complete Lifecycle

// Client-side lifecycle { // Initialization onInit: async (store) => { ... }, // Authentication onAuth: async (token) => { ... }, onUnauthenticated: async () => { ... }, // Bootstrap beforeBootstrap: async () => { ... }, afterBootstrap: async (rows) => { ... }, // Delta Sync beforeDelta: async (since) => { ... }, afterDelta: async (frames) => { ... }, // Mutations beforeMutation: async (mutation) => { ... }, afterMutation: async (mutation, result) => { ... }, // Frames onFrame: async (frame) => { ... }, // Network onOnline: async () => { ... }, onOffline: async () => { ... }, // Errors onError: async (error, context) => { ... }, onConflict: async (conflict) => { ... } }

Use Cases

Feature Flagging

middleware: { beforeMutation: async (mutation) => { const flags = await featureFlags.get(userId) if (mutation.type === 'post' && !flags.canCreatePosts) { throw new Error('Feature not enabled for this user') } return mutation } }

Data Encryption

middleware: { beforeMutation: async (mutation) => { if (mutation.type === 'message') { const patch = { ...mutation.patch } // Encrypt content if (patch.content) { patch.content = await encrypt(patch.content) } return { ...mutation, patch } } return mutation }, onFrame: async (frame) => { if (frame.t === 'message' && frame.p?.content) { // Decrypt content frame.p.content = await decrypt(frame.p.content) } return frame } }

Quota Enforcement

middleware: { beforeMutation: async (mutation, context) => { if (mutation.op === 'i') { const count = await prisma[mutation.type].count({ where: { userId: context.userId } }) const quota = await getQuota(context.userId, mutation.type) if (count >= quota) { throw new Error(`Quota exceeded: max ${quota} ${mutation.type}s`) } } return mutation } }

Chaining Middleware

Multiple middleware functions are chained:

middleware: { beforeMutation: [ validateMutation, checkQuota, transformData, logMutation ] } // Executed in order: // 1. validateMutation(mutation) // 2. checkQuota(mutation) // 3. transformData(mutation) // 4. logMutation(mutation) // 5. Actually send to server

Async Middleware

All middleware can be async:

beforeMutation: async (mutation) => { // Async operations allowed const userPermissions = await fetchPermissions(userId) if (!userPermissions.canEdit) { throw new Error('No permission') } return mutation }

Next Steps

Last updated on