@cloudillo/core
Overview
The @cloudillo/core library is the core SDK for Cloudillo applications. It provides initialization via the message bus, API client creation, CRDT document support, and URL helpers.
Installation
pnpm add @cloudillo/coreCore Pattern: AppMessageBus
The main API is accessed through the getAppBus() singleton. This provides authentication state, storage, and shell communication for apps running in the Cloudillo shell.
import { getAppBus, openYDoc } from '@cloudillo/core'
// Get the singleton message bus
const bus = getAppBus()
// Initialize your app (communicates with shell)
const state = await bus.init('my-app')
// Access state via bus properties
console.log('Token:', bus.accessToken)
console.log('User ID:', bus.idTag)
console.log('Tenant ID:', bus.tnId)
console.log('Roles:', bus.roles)
console.log('Access level:', bus.access)
console.log('Dark mode:', bus.darkMode)AppState Properties
After calling bus.init(), these properties are available on the bus:
| Property | Type | Description |
|---|---|---|
accessToken |
string | undefined |
Current JWT access token |
idTag |
string | undefined |
User’s identity tag (e.g., “alice.cloudillo.net”) |
tnId |
number | undefined |
Tenant ID |
roles |
string[] | undefined |
User’s roles |
access |
'read' | 'write' |
Access level to current resource |
darkMode |
boolean |
Dark mode preference |
tokenLifetime |
number | undefined |
Token lifetime in seconds |
displayName |
string | undefined |
Display name (for anonymous guests) |
Storage API
The bus provides namespaced key-value storage:
const bus = getAppBus()
await bus.init('my-app')
// Store data
await bus.storage.set('my-app', 'settings', { theme: 'dark' })
// Retrieve data
const settings = await bus.storage.get<{ theme: string }>('my-app', 'settings')
// List keys
const keys = await bus.storage.list('my-app', 'user-')
// Delete data
await bus.storage.delete('my-app', 'settings')
// Clear namespace
await bus.storage.clear('my-app')
// Check quota
const quota = await bus.storage.quota('my-app')
console.log(`Used ${quota.used} of ${quota.limit} bytes`)App Lifecycle Notifications
Notify the shell about your app’s loading progress:
const bus = getAppBus()
await bus.init('my-app')
// After auth init (called automatically by init())
bus.notifyReady('auth')
// After CRDT sync complete
bus.notifyReady('synced')
// When fully interactive
bus.notifyReady('ready')Token Refresh
Request a fresh token when needed:
const bus = getAppBus()
// Manually refresh token
const newToken = await bus.refreshToken()
// Listen for token updates pushed from shell
bus.on('auth:token.push', (msg) => {
console.log('Token updated:', bus.accessToken)
})API Client
createApiClient(opts: ApiClientOpts): ApiClient
Create a type-safe REST API client.
import { createApiClient } from '@cloudillo/core'
const api = createApiClient({
idTag: 'alice.cloudillo.net', // Required: target tenant
authToken: 'jwt-token' // Optional: authentication token
})
// Use the API
const profile = await api.profiles.getOwn()
const posts = await api.actions.list({ type: 'POST', limit: 20 })Options:
interface ApiClientOpts {
idTag: string // Required: identity tag of the tenant
authToken?: string // Optional: JWT token for authentication
}idTag is Required
Unlike the old documentation, idTag is required to create an API client. This specifies which Cloudillo instance to connect to.
Using with AppMessageBus
import { getAppBus, createApiClient } from '@cloudillo/core'
const bus = getAppBus()
const state = await bus.init('my-app')
const api = createApiClient({
idTag: bus.idTag!,
authToken: bus.accessToken
})
const files = await api.files.list({ limit: 20 })CRDT Document Functions
openYDoc(yDoc: Y.Doc, docId: string): Promise<DocConnection>
Open a Yjs document for collaborative editing with WebSocket synchronization.
import { getAppBus, openYDoc } from '@cloudillo/core'
import * as Y from 'yjs'
const bus = getAppBus()
await bus.init('my-app')
const yDoc = new Y.Doc()
const { yDoc: doc, provider } = await openYDoc(yDoc, 'alice.cloudillo.net: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)
})Parameters:
yDoc- The Yjs document to synchronizedocId- Document ID in format"targetTag:resourceId"
Returns:
interface DocConnection {
yDoc: Y.Doc
provider: WebsocketProvider
}Error Handling:
- Throws if no access token (must call
init()first) - Throws if
docIdformat is invalid - WebSocket close codes 4401/4403/4404 stop reconnection (auth/permission/not found errors)
URL Helper Functions
Build URLs for Cloudillo services:
getInstanceUrl(idTag: string): string
Build the base URL for a Cloudillo instance.
import { getInstanceUrl } from '@cloudillo/core'
const url = getInstanceUrl('alice.cloudillo.net')
// Returns: "https://cl-o.alice.cloudillo.net"
getApiUrl(idTag: string): string
Build the API base URL.
import { getApiUrl } from '@cloudillo/core'
const url = getApiUrl('alice.cloudillo.net')
// Returns: "https://cl-o.alice.cloudillo.net/api"
getFileUrl(idTag: string, fileId: string, variant?: string): string
Build URL for file access with optional variant.
import { getFileUrl } from '@cloudillo/core'
// Basic file URL
const url = getFileUrl('alice.cloudillo.net', 'file-123')
// Returns: "https://cl-o.alice.cloudillo.net/api/files/file-123"
// With variant
const thumbnailUrl = getFileUrl('alice.cloudillo.net', 'file-123', 'vis.tn')
// Returns: "https://cl-o.alice.cloudillo.net/api/files/file-123?variant=vis.tn"
getCrdtUrl(idTag: string): string
Build the CRDT WebSocket URL.
import { getCrdtUrl } from '@cloudillo/core'
const url = getCrdtUrl('alice.cloudillo.net')
// Returns: "wss://cl-o.alice.cloudillo.net/ws/crdt"
getRtdbUrl(idTag: string, fileId: string, token: string): string
Build the RTDB WebSocket URL with authentication.
import { getRtdbUrl } from '@cloudillo/core'
const url = getRtdbUrl('alice.cloudillo.net', 'db-file-id', 'jwt-token')
// Returns: "wss://cl-o.alice.cloudillo.net/ws/rtdb/db-file-id?token=jwt-token"
getMessageBusUrl(idTag: string, token: string): string
Build the Message Bus WebSocket URL.
import { getMessageBusUrl } from '@cloudillo/core'
const url = getMessageBusUrl('alice.cloudillo.net', 'jwt-token')
// Returns: "wss://cl-o.alice.cloudillo.net/ws/bus?token=jwt-token"
Image/Video Variant Helpers
getOptimalImageVariant(context, localVariants?): string
Get the optimal image variant for a display context.
import { getOptimalImageVariant } from '@cloudillo/core'
// For thumbnail display
const variant = getOptimalImageVariant('thumbnail') // 'vis.tn'
// For preview
const variant = getOptimalImageVariant('preview') // 'vis.sd'
// For fullscreen/lightbox
const variant = getOptimalImageVariant('fullscreen') // 'vis.hd'
// With available variants
const variant = getOptimalImageVariant('fullscreen', ['vis.sd', 'vis.hd', 'vis.xd'])
// Returns highest quality available: 'vis.xd'
Image variants: vis.tn (150px), vis.sd (640px), vis.md (1280px), vis.hd (1920px), vis.xd (original)
getOptimalVideoVariant(context, localVariants?): string
Get the optimal video variant for a display context.
import { getOptimalVideoVariant } from '@cloudillo/core'
const variant = getOptimalVideoVariant('preview') // 'vid.sd'
const variant = getOptimalVideoVariant('fullscreen') // 'vid.hd'
Video variants: vid.sd, vid.md, vid.hd, vid.xd
getImageVariantForDisplaySize(width, height): string
Get optimal variant for a specific display size in pixels.
import { getImageVariantForDisplaySize } from '@cloudillo/core'
// For a 300x200 canvas at 2x zoom (600x400 screen pixels)
const variant = getImageVariantForDisplaySize(600, 400)
// Returns: 'vis.sd'
API Client Structure
The API client provides access to all REST endpoints organized by namespace:
const api = createApiClient({ idTag: 'alice.cloudillo.net', authToken: token })
// ============================================================================
// AUTH - Authentication & Authorization
// ============================================================================
await api.auth.login({ idTag, password })
await api.auth.logout()
await api.auth.getLoginToken()
await api.auth.getAccessToken({ scope, lifetime })
await api.auth.getAccessTokenByRef(refId) // Guest access via reference
await api.auth.getAccessTokenByApiKey(apiKey) // API key authentication
await api.auth.getProxyToken(targetIdTag) // Federation proxy token
await api.auth.getVapidPublicKey() // Push notification key
await api.auth.changePassword({ oldPassword, newPassword })
await api.auth.setPassword({ refId, password }) // Set password via reset link
await api.auth.forgotPassword({ email })
// WebAuthn
await api.auth.listWebAuthnCredentials()
await api.auth.getWebAuthnRegChallenge()
await api.auth.registerWebAuthnCredential({ token, credential })
await api.auth.deleteWebAuthnCredential(credentialId)
await api.auth.getWebAuthnLoginChallenge()
await api.auth.webAuthnLogin({ token, credential })
// API Keys
await api.auth.listApiKeys()
await api.auth.createApiKey({ name, scopes })
await api.auth.deleteApiKey(keyId)
// ============================================================================
// PROFILE - Registration & Profile Creation
// ============================================================================
await api.profile.verify({ type, idTag, email }) // Check availability
await api.profile.register({ type, idTag, name, email, password })
// ============================================================================
// PROFILES - Profile Management
// ============================================================================
await api.profiles.getOwn() // GET /me
await api.profiles.getOwnFull() // GET /me/full
await api.profiles.updateOwn({ name, bio }) // PATCH /me
await api.profiles.list({ q, type, role }) // List/search profiles
await api.profiles.get(idTag) // Get profile by idTag
await api.profiles.updateConnection(idTag, data) // Update relationship
await api.profiles.adminUpdate(idTag, { status, roles }) // Admin operations
// ============================================================================
// ACTIONS - Social Interactions
// ============================================================================
await api.actions.list({ type, status, limit }) // List actions
await api.actions.listPaginated({ cursor, limit }) // With cursor pagination
await api.actions.create({ type, content, ... }) // Create action
await api.actions.get(actionId) // Get single action
await api.actions.update(actionId, patch) // Update draft
await api.actions.delete(actionId) // Delete action
await api.actions.accept(actionId) // Accept (e.g., connection)
await api.actions.reject(actionId) // Reject
await api.actions.updateStat(actionId, stat) // Update statistics
await api.actions.addReaction(actionId, { type }) // Add reaction
// ============================================================================
// FILES - File Management
// ============================================================================
await api.files.list({ parentId, tag, limit }) // List files
await api.files.listPaginated({ cursor, limit }) // With cursor pagination
await api.files.create({ fileName, fileTp }) // Create metadata-only file
await api.files.uploadBlob(preset, name, data, contentType) // Upload file
await api.files.get(fileId) // Get file content
await api.files.getVariant(variantId) // Get specific variant
await api.files.getDescriptor(fileId) // Get file metadata
await api.files.update(fileId, { fileName }) // Update metadata
await api.files.delete(fileId) // Soft delete (to trash)
await api.files.permanentDelete(fileId) // Permanent delete
await api.files.restore(fileId, parentId) // Restore from trash
await api.files.updateUserData(fileId, { starred }) // Update user-specific data
await api.files.setStarred(fileId, true) // Toggle starred
await api.files.setPinned(fileId, true) // Toggle pinned
await api.files.addTag(fileId, 'tag') // Add tag
await api.files.removeTag(fileId, 'tag') // Remove tag
await api.files.favorite(fileId) // Add to favorites
await api.files.unfavorite(fileId) // Remove from favorites
await api.files.listFavorites({ limit }) // List favorites
await api.files.listRecent({ limit }) // List recent files
// ============================================================================
// TRASH - Trash Management
// ============================================================================
await api.trash.list({ limit }) // List trashed files
await api.trash.empty() // Empty trash permanently
// ============================================================================
// COLLECTIONS - Favorites, Bookmarks, Pins
// ============================================================================
await api.collections.list('FAVR', { limit }) // List collection items
await api.collections.add('BKMK', itemId) // Add to collection
await api.collections.remove('PIND', itemId) // Remove from collection
// Collection types: 'FAVR' | 'RCNT' | 'BKMK' | 'PIND'
// ============================================================================
// TAGS - Tag Management
// ============================================================================
await api.tags.list({ prefix, withCounts }) // List tags
// ============================================================================
// SETTINGS - User Settings
// ============================================================================
await api.settings.list({ prefix }) // List settings
await api.settings.get(name) // Get setting value
await api.settings.update(name, { value }) // Update setting
// ============================================================================
// NOTIFICATIONS - Push Notifications
// ============================================================================
await api.notifications.subscribe({ subscription }) // Subscribe to push
// ============================================================================
// REFS - Share Links & References
// ============================================================================
await api.refs.list({ type, resourceId }) // List references
await api.refs.get(refId) // Get reference
await api.refs.create({ type, resourceId, access }) // Create share link
await api.refs.delete(refId) // Delete reference
// ============================================================================
// IDP - Identity Provider (End User)
// ============================================================================
await api.idp.getInfo(providerDomain) // Get provider info
await api.idp.activate({ refId }) // Activate identity
// ============================================================================
// IDP MANAGEMENT - Identity Provider Admin
// ============================================================================
await api.idpManagement.listIdentities({ q, status })
await api.idpManagement.createIdentity({ idTag, email, createApiKey })
await api.idpManagement.getIdentity(idTag)
await api.idpManagement.updateIdentity(idTag, { dyndns })
await api.idpManagement.deleteIdentity(idTag)
await api.idpManagement.listApiKeys(idTag)
await api.idpManagement.createApiKey({ idTag, name })
await api.idpManagement.deleteApiKey(keyId, idTag)
// ============================================================================
// COMMUNITIES - Community Management
// ============================================================================
await api.communities.create(idTag, { type, name, ownerIdTag })
await api.communities.verify({ type, idTag }) // Deprecated: use profile.verify
// ============================================================================
// ADMIN - System Administration
// ============================================================================
await api.admin.listTenants({ q, status, limit }) // List all tenants
await api.admin.sendPasswordReset(idTag) // Send password reset
await api.admin.sendTestEmail(to) // Test SMTP config
Helper Functions
delay(ms: number): Promise<void>
Delay execution for a specified time.
import { delay } from '@cloudillo/core'
await delay(1000) // Wait 1 second
See Also
- @cloudillo/react - React integration with hooks
- @cloudillo/rtdb - Real-time database
- REST API Reference - Complete API documentation