Skip to main content

Overview

The Settings system provides a structured way to manage application configuration and preferences. Unlike entries, settings are singleton objects designed for application-wide or user-specific configuration that doesn’t require full CRUD operations.

What are Settings?

Settings are key-value stores for configuration data. Each settings type represents a distinct configuration group, such as:
  • Application preferences (theme, language, etc.)
  • Feature flags
  • System configuration
  • User preferences
  • Integration credentials

Settings Interface

The base settings interface is flexible:
import type { Settings } from '@inspatial/cloud-client/types';

interface Settings {
  [key: string]: any;
}

// Define your own settings types
interface AppSettings extends Settings {
  theme: 'light' | 'dark' | 'auto';
  language: string;
  notifications: boolean;
  pageSize: number;
}

interface FeatureFlags extends Settings {
  enableBetaFeatures: boolean;
  enableAnalytics: boolean;
  maintenanceMode: boolean;
}

Settings with Timestamps

Track when individual settings fields were last modified:
import type { SettingsWithTimestamp } from '@inspatial/cloud-client/types';

interface SettingsWithTimestamp<S extends Settings> {
  data: S;                    // Settings values
  updatedAt: {               // Modification timestamps
    [K in keyof S]: number;  // Unix timestamp per field
  };
}
This is useful for:
  • Conflict resolution
  • Audit trails
  • Determining what changed
  • Implementing “last writer wins” logic

Settings Operations

Getting Settings

import { InCloudClient } from '@inspatial/cloud-client';

const client = new InCloudClient();

// Get settings without timestamps
const settings = await client.settings.getSettings<AppSettings>('AppSettings');

console.log(settings.theme);        // 'dark'
console.log(settings.language);     // 'en'
console.log(settings.notifications); // true

Getting Settings with Timestamps

const { data, updatedAt } = await client.settings.getSettingsWithModifiedTime<AppSettings>(
  'AppSettings'
);

console.log(data.theme);              // 'dark'
console.log(updatedAt.theme);         // 1704067200000
console.log(updatedAt.language);      // 1703980800000

// Check when settings were last modified
const themeLastModified = new Date(updatedAt.theme);
console.log(`Theme changed on ${themeLastModified.toLocaleString()}`);

Updating Settings

// Update specific settings (partial update)
const updated = await client.settings.updateSettings<AppSettings>(
  'AppSettings',
  {
    theme: 'dark',
    pageSize: 50
  }
);

console.log(updated); // Full settings object with updates applied
Settings updates are partial by default. Only the fields you provide will be updated; others remain unchanged.

Settings Actions

Like entries, settings can have custom actions:
interface SettingsAction {
  key: string;              // Action identifier
  label?: string;           // Display name
  description?: string;     // Action description
  params: InField[];        // Required parameters
}

Running Settings Actions

// Run action immediately
const result = await client.settings.runSettingsAction<InputData, OutputData>(
  'AppSettings',
  'reset',
  {},
  false  // Don't enqueue
);

// Enqueue action for background processing
await client.settings.runSettingsAction(
  'IntegrationSettings',
  'syncWithProvider',
  { provider: 'stripe' },
  true  // Enqueue
);

Settings Types

Settings types define the structure and behavior:
interface SettingsType {
  config: SettingsTypeConfig;     // Base configuration
  actions: SettingsAction[];      // Custom actions
  permission: Record<string, unknown>;  // Access control
}

interface SettingsTypeConfig {
  // Base configuration properties
}

Real-time Settings Updates

Use InLiveClient to receive real-time settings updates:
import { InLiveClient } from '@inspatial/cloud-client';
import type { SettingsListener } from '@inspatial/cloud-client/types';

const liveClient = new InLiveClient();
liveClient.start();

interface AppSettings {
  theme: 'light' | 'dark' | 'auto';
  language: string;
  notifications: boolean;
}

const settingsListener: SettingsListener<AppSettings> = {
  name: 'app-settings-listener',
  callback: (event, data) => {
    if (event === 'update') {
      console.log('Settings updated:', data);
      
      // Apply settings changes
      if (data.theme) {
        applyTheme(data.theme);
      }
      if (data.language) {
        changeLanguage(data.language);
      }
    }
  }
};

liveClient.onSettings<typeof settingsListener>('AppSettings', settingsListener);

Settings Events

Event Types

type SettingsEventMap<S extends Settings> = {
  update: S;                          // Settings updated
  join: Record<string, unknown>;      // Joined settings room
  leave: Record<string, unknown>;     // Left settings room
};

Settings Listener

type SettingsListener<
  S extends Settings,
  E extends keyof SettingsEventMap<S> = keyof SettingsEventMap<S>
> = {
  name: string;
  callback(event: E, data: SettingsEventMap<S>[E]): Promise<void> | void;
};

Use Cases

Application Preferences

interface UserPreferences extends Settings {
  theme: 'light' | 'dark' | 'auto';
  language: string;
  timezone: string;
  emailNotifications: boolean;
  pushNotifications: boolean;
  displayDensity: 'comfortable' | 'compact';
}

class PreferencesManager {
  constructor(private client: InCloudClient) {}

  async loadPreferences(): Promise<UserPreferences> {
    return await this.client.settings.getSettings<UserPreferences>(
      'UserPreferences'
    );
  }

  async updateTheme(theme: 'light' | 'dark' | 'auto') {
    return await this.client.settings.updateSettings<UserPreferences>(
      'UserPreferences',
      { theme }
    );
  }

  async updateNotificationSettings(email: boolean, push: boolean) {
    return await this.client.settings.updateSettings<UserPreferences>(
      'UserPreferences',
      {
        emailNotifications: email,
        pushNotifications: push
      }
    );
  }
}

Feature Flags

interface FeatureFlags extends Settings {
  enableBetaFeatures: boolean;
  enableNewDashboard: boolean;
  enableAdvancedSearch: boolean;
  maintenanceMode: boolean;
  maxUploadSize: number;
}

class FeatureManager {
  private flags: FeatureFlags | null = null;

  constructor(
    private client: InCloudClient,
    private liveClient: InLiveClient
  ) {
    this.setupLiveUpdates();
  }

  async initialize() {
    this.flags = await this.client.settings.getSettings<FeatureFlags>(
      'FeatureFlags'
    );
  }

  isEnabled(feature: keyof FeatureFlags): boolean {
    return this.flags?.[feature] === true;
  }

  private setupLiveUpdates() {
    const listener: SettingsListener<FeatureFlags> = {
      name: 'feature-flags-listener',
      callback: (event, data) => {
        if (event === 'update') {
          this.flags = { ...this.flags, ...data };
          console.log('Feature flags updated:', data);
        }
      }
    };

    this.liveClient.onSettings<typeof listener>('FeatureFlags', listener);
  }
}

System Configuration

interface SystemConfig extends Settings {
  apiVersion: string;
  maintenanceWindow: {
    start: number;
    end: number;
  };
  rateLimits: {
    api: number;
    uploads: number;
  };
  allowedOrigins: string[];
}

class SystemManager {
  constructor(private client: InCloudClient) {}

  async getConfig(): Promise<SystemConfig> {
    return await this.client.settings.getSettings<SystemConfig>('SystemConfig');
  }

  async updateRateLimits(api: number, uploads: number) {
    return await this.client.settings.updateSettings<SystemConfig>(
      'SystemConfig',
      {
        rateLimits: { api, uploads }
      }
    );
  }

  async scheduleMaintenanceWindow(start: number, end: number) {
    return await this.client.settings.updateSettings<SystemConfig>(
      'SystemConfig',
      {
        maintenanceWindow: { start, end }
      }
    );
  }
}

Best Practices

Type Definitions

Define TypeScript interfaces for your settings to ensure type safety across your application.

Partial Updates

Only update the settings that changed to reduce payload size and prevent accidental overwrites.

Live Sync

Use InLiveClient to keep settings in sync across multiple tabs or devices.

Timestamps

Use getSettingsWithModifiedTime when you need to track changes or resolve conflicts.

Settings vs Entries

Choose Settings when:
  • You need a singleton configuration object
  • The data represents application or user preferences
  • You don’t need full CRUD operations
  • The data structure is relatively flat
Choose Entries when:
  • You need multiple records of the same type
  • You need full CRUD operations
  • You need relationships between records
  • You need advanced querying and filtering

Complete Example

import { InCloudClient, InLiveClient } from '@inspatial/cloud-client';
import type { SettingsListener } from '@inspatial/cloud-client/types';

interface AppSettings {
  theme: 'light' | 'dark' | 'auto';
  language: string;
  notifications: boolean;
  pageSize: number;
  sidebarCollapsed: boolean;
}

class SettingsService {
  private settings: AppSettings | null = null;
  private client: InCloudClient;
  private liveClient: InLiveClient;

  constructor() {
    this.client = new InCloudClient();
    this.liveClient = new InLiveClient();
    this.setupLiveUpdates();
  }

  async initialize() {
    // Load initial settings
    this.settings = await this.client.settings.getSettings<AppSettings>(
      'AppSettings'
    );
    
    // Apply settings
    this.applySettings(this.settings);
    
    // Start live connection
    this.liveClient.start();
  }

  async updateTheme(theme: 'light' | 'dark' | 'auto') {
    const updated = await this.client.settings.updateSettings<AppSettings>(
      'AppSettings',
      { theme }
    );
    this.settings = updated;
    this.applySettings(updated);
  }

  async updateLanguage(language: string) {
    const updated = await this.client.settings.updateSettings<AppSettings>(
      'AppSettings',
      { language }
    );
    this.settings = updated;
    this.applySettings(updated);
  }

  async toggleSidebar() {
    const collapsed = !this.settings?.sidebarCollapsed;
    await this.client.settings.updateSettings<AppSettings>(
      'AppSettings',
      { sidebarCollapsed: collapsed }
    );
  }

  private setupLiveUpdates() {
    const listener: SettingsListener<AppSettings> = {
      name: 'settings-sync',
      callback: (event, data) => {
        if (event === 'update') {
          console.log('Settings updated remotely:', data);
          this.settings = { ...this.settings, ...data } as AppSettings;
          this.applySettings(data);
        }
      }
    };

    this.liveClient.onSettings<typeof listener>('AppSettings', listener);
  }

  private applySettings(settings: Partial<AppSettings>) {
    if (settings.theme) {
      document.documentElement.setAttribute('data-theme', settings.theme);
    }
    if (settings.language) {
      // Change app language
    }
    if (settings.sidebarCollapsed !== undefined) {
      // Update sidebar state
    }
  }

  getSettings(): AppSettings | null {
    return this.settings;
  }
}

// Usage
const settingsService = new SettingsService();
await settingsService.initialize();

// User changes theme
await settingsService.updateTheme('dark');

// User changes language
await settingsService.updateLanguage('es');

Next Steps

Cloud Client

Learn about the main API client

Live Updates

Subscribe to real-time settings changes

Entries

Understand when to use entries vs settings

API Reference

View complete settings API

Build docs developers (and LLMs) love