@cloudillo/base

@cloudillo/base

The @cloudillo/base library is the core SDK for Cloudillo applications. It provides initialization, API client creation, and helpers for real-time features.

Installation

pnpm add @cloudillo/base

Core Functions

init(appName: string): Promise

Initialize your Cloudillo application and get an access token.

For Microfrontends: When running inside the Cloudillo shell, init() automatically communicates with the shell via postMessage to receive authentication credentials.

For Standalone Apps: You must manually set cloudillo.accessToken before calling init().

import * as cloudillo from '@cloudillo/base'

// Initialize your app
const token = await cloudillo.init('my-app-name')

console.log('Token:', token)
console.log('User ID:', cloudillo.idTag)
console.log('Tenant ID:', cloudillo.tnId)
console.log('Roles:', cloudillo.roles)

Returns: Access token (JWT string)

Side Effects:

  • Sets cloudillo.accessToken
  • Sets cloudillo.idTag
  • Sets cloudillo.tnId
  • Sets cloudillo.roles
  • Sets cloudillo.darkMode

createApiClient(opts?: ApiClientOptions): ApiClient

Create a type-safe REST API client.

import * as cloudillo from '@cloudillo/base'

await cloudillo.init('my-app')

const api = cloudillo.createApiClient()

// Now use the API
const profile = await api.me.get()
const posts = await api.action.get({ type: 'POST', _limit: 20 })

Options:

interface ApiClientOptions {
  baseUrl?: string // Default: current origin
  token?: string // Default: cloudillo.accessToken
  fetch?: typeof fetch // Custom fetch implementation
}

Example with options:

const api = cloudillo.createApiClient({
  baseUrl: 'https://api.cloudillo.com',
  token: 'custom-token'
})

Real-Time Functions

openYDoc(yDoc: Y.Doc, docId: string): Promise

Open a CRDT document for collaborative editing using Yjs.

import * as cloudillo from '@cloudillo/base'
import * as Y from 'yjs'

await cloudillo.init('my-app')

const yDoc = new Y.Doc()
const { provider } = await cloudillo.openYDoc(yDoc, 'my-document-id')

// Use shared types
const yText = yDoc.getText('content')
yText.insert(0, 'Hello, collaborative world!')

// Listen for changes
yText.observe(() => {
  console.log('Text changed:', yText.toString())
})

// Access awareness (other users' cursors, selections)
provider.awareness.on('change', () => {
  const states = provider.awareness.getStates()
  console.log('Connected users:', states.size)
})

Returns:

interface DocConnection {
  yDoc: Y.Doc
  provider: WebsocketProvider
}

openCRDT(yDoc: Y.Doc, docId: string, opts?: CrdtOptions): Promise

Lower-level function for opening CRDT connections with custom options.

const provider = await cloudillo.openCRDT(yDoc, 'doc-id', {
  url: 'wss://custom-server.com/ws/crdt',
  token: 'custom-token'
})

Options:

interface CrdtOptions {
  url?: string // WebSocket URL
  token?: string // Access token
  params?: Record<string, string> // Query parameters
}

openRTDB(fileId: string, opts?: RtdbOptions): RtdbClient

Open a real-time database connection.

import * as cloudillo from '@cloudillo/base'

await cloudillo.init('my-app')

const rtdb = cloudillo.openRTDB('my-database-file-id')

const todos = rtdb.collection('todos')
todos.onSnapshot(snapshot => {
  console.log('Todos:', snapshot)
})

See RTDB documentation for full API details.

openMessageBus(opts?: MessageBusOptions): MessageBusClient

Open a message bus connection for pub/sub messaging.

import * as cloudillo from '@cloudillo/base'

await cloudillo.init('my-app')

const bus = cloudillo.openMessageBus()

// Subscribe to a channel
bus.subscribe('notifications', (message) => {
  console.log('Notification:', message)
})

// Publish a message
bus.publish('notifications', {
  type: 'new-post',
  data: { postId: '123' }
})

Options:

interface MessageBusOptions {
  url?: string // WebSocket URL
  token?: string // Access token
  channels?: string[] // Auto-subscribe to channels
}

Global State

After calling init(), these global variables are available:

cloudillo.accessToken: string

The current access token (JWT).

console.log('Token:', cloudillo.accessToken)

// Manually set token (for standalone apps)
cloudillo.accessToken = 'your-jwt-token'

cloudillo.idTag: string

The current user’s identity tag.

console.log('User:', cloudillo.idTag) // e.g., "alice@example.com"

cloudillo.tnId: number

The current tenant ID.

console.log('Tenant:', cloudillo.tnId) // e.g., 12345

cloudillo.roles: string[]

The current user’s roles.

console.log('Roles:', cloudillo.roles) // e.g., ["user", "admin"]

if (cloudillo.roles.includes('admin')) {
  console.log('User is an admin')
}

cloudillo.darkMode: boolean

The current theme preference (light or dark).

if (cloudillo.darkMode) {
  document.body.classList.add('dark-theme')
}

Helper Functions

qs(params: Record<string, any>): string

Convert an object to a query string.

import { qs } from '@cloudillo/base'

const query = qs({ type: 'POST', _limit: 20, status: 'A' })
console.log(query) // "type=POST&_limit=20&status=A"

// Use in API calls
const url = `/action?${qs({ type: 'POST', _limit: 20 })}`

parseQS(search: string): Record<string, string>

Parse a query string into an object.

import { parseQS } from '@cloudillo/base'

const params = parseQS('?type=POST&_limit=20')
console.log(params) // { type: 'POST', _limit: '20' }

apiFetchHelper(url: string, options?: RequestInit): Promise

Low-level fetch helper with automatic authentication.

import { apiFetchHelper } from '@cloudillo/base'

const response = await apiFetchHelper('/auth/me')
const profile = await response.json()

This automatically:

  • Adds Authorization: Bearer <token> header
  • Uses the current cloudillo.accessToken
  • Throws FetchError on HTTP errors

Error Handling

FetchError

All API errors are thrown as FetchError instances.

import { FetchError } from '@cloudillo/base'

try {
  const api = cloudillo.createApiClient()
  const profile = await api.me.get()
} catch (error) {
  if (error instanceof FetchError) {
    console.error('HTTP Status:', error.status) // 401, 404, etc.
    console.error('Error Code:', error.code) // 'E-AUTH-UNAUTH', etc.
    console.error('Message:', error.message)
    console.error('Response:', error.response) // Full response object
  } else {
    console.error('Unexpected error:', error)
  }
}

Properties:

  • status: number - HTTP status code
  • code: string - Cloudillo error code (e.g., ‘E-AUTH-UNAUTH’)
  • message: string - Error message
  • response: Response - Full fetch Response object

API Client Structure

The API client provides access to all REST endpoints:

const api = cloudillo.createApiClient()

// Authentication
await api.auth.login.post({ idTag, password })
await api.auth.logout.post()
await api.auth.loginToken.get()
await api.auth.accessToken.get({ idTag, password })
await api.auth.proxyToken.get({ target })

// Profile
await api.me.get()
await api.me.patch({ name: 'New Name' })
await api.profile.get({ idTag: 'alice@example.com' })
await api.profile.list.get({ type: 'person', _limit: 20 })

// Actions
await api.action.get({ type: 'POST', _limit: 20 })
await api.action.post({ type: 'POST', content: { text: 'Hello!' } })
await api.action.id(actionId).get()
await api.action.id(actionId).patch({ content: { text: 'Updated' } })
await api.action.id(actionId).delete()
await api.action.id(actionId).accept.post()
await api.action.id(actionId).reject.post()
await api.action.id(actionId).reaction.post({ type: 'LOVE' })

// Files
await api.file.get({ fileTp: 'BLOB', _limit: 20 })
await api.file.post({ fileTp: 'BLOB', contentType: 'image/png' })
await api.file.id(fileId).get()
await api.file.id(fileId).descriptor.get()
await api.file.id(fileId).patch({ tags: ['important'] })
await api.file.id(fileId).delete()
await api.file.upload.post(formData)

// Settings
await api.settings.get()
await api.settings.name(settingName).get()
await api.settings.name(settingName).put(value)

// References
await api.ref.get()
await api.ref.post({ name: 'Bookmark', url: 'https://example.com' })
await api.ref.id(refId).delete()

// Tags
await api.tag.get({ prefix: 'proj-' })

WebSocket Helpers

WebSocketClient

Low-level WebSocket client with automatic reconnection.

import { WebSocketClient } from '@cloudillo/base'

const ws = new WebSocketClient({
  url: 'wss://server.com/ws/endpoint',
  token: 'your-token',
  onMessage: (message) => {
    console.log('Received:', message)
  },
  onConnect: () => {
    console.log('Connected')
  },
  onDisconnect: () => {
    console.log('Disconnected')
  }
})

// Send a message
ws.send({ type: 'subscribe', channel: 'updates' })

// Close connection
ws.close()

Features:

  • Automatic reconnection with exponential backoff
  • Authentication via query parameter
  • JSON message encoding/decoding
  • Event handlers for connect/disconnect/message

Advanced Usage

Custom Fetch Implementation

import * as cloudillo from '@cloudillo/base'

// Use a custom fetch implementation (e.g., for testing)
const api = cloudillo.createApiClient({
  fetch: async (url, options) => {
    console.log('Fetching:', url)
    return fetch(url, options)
  }
})

Multiple API Clients

// Different servers or tokens
const api1 = cloudillo.createApiClient({
  baseUrl: 'https://server1.com',
  token: 'token1'
})

const api2 = cloudillo.createApiClient({
  baseUrl: 'https://server2.com',
  token: 'token2'
})

Standalone App Pattern

import * as cloudillo from '@cloudillo/base'

// Manual authentication
async function login(idTag: string, password: string) {
  const response = await fetch('/auth/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ idTag, password })
  })

  const data = await response.json()

  // Set global state
  cloudillo.accessToken = data.data.token
  cloudillo.tnId = data.data.tnId
  cloudillo.idTag = data.data.idTag
  cloudillo.roles = data.data.roles

  // Now init() will use these values
  await cloudillo.init('my-app')

  return data.data
}

See Also