Learn how to register your application with the Asterisms ecosystem using the SDK Backend.
The registration service handles application registration within the Asterisms ecosystem. Registration typically happens automatically during SDK initialization, but you can also manually register your application when needed.
const registration = sdk.registration();// Get current product information
const information = sdk.information();
const currentProduct = information.currentProduct();
console.log('Current Product:', {
bundleId: currentProduct.bundleId,
name: currentProduct.name,
version: currentProduct.version,
capabilities: currentProduct.capabilities,
subdomain: currentProduct.subdomain,
description: currentProduct.description
});
// Get root domain
const rootDomain = information.rootDomain();
console.log('Root Domain:', rootDomain);import { type ProductRegistrationData } from '@asterisms/common-core';
try {
const registrationData: ProductRegistrationData = {
product: {
bundleId: 'com.example.myapp',
name: 'MyApp',
title: 'My Business App',
description: 'A comprehensive business management application',
subdomain: 'myapp',
capabilities: ['FRONTEND_WEBSITE', 'API_SERVICE'],
group: 'productivity',
version: '1.0.0',
build: 'abc123',
sdktype: 'JAVASCRIPT'
},
version: '1.0.0',
build: 'abc123',
sdktype: 'JAVASCRIPT'
};
const response = await registration.register(registrationData);
console.log('Registration successful:', response.entity.product);
} catch (error) {
console.error('Registration failed:', error);
}import { type ProductRegistrationData, type RegistrationData } from '@asterisms/common-core';
interface ProductRegistrationData {
product: RegistrationData;
version: string;
build: string;
sdktype: 'JAVASCRIPT';
supportedFeatures?: SupportedFeature[];
frontendWebsites?: RegistrationData[];
}
interface RegistrationData {
bundleId: string;
name: string;
title: string;
description: string;
subdomain: string;
capabilities: ProductCapability[];
group?: string;
version: string;
build: string;
sdktype: 'JAVASCRIPT';
icon?: string;
order?: number;
}// Registration typically happens automatically during SDK initialization
const sdk = getAsterismsBackendSDK({
bundleId: 'com.example.myapp',
domain: 'asterisms.example.com',
subdomain: 'myapp',
httpServiceAdapterFactory: createFetchAdapter(fetch),
registrationData: {
bundleId: 'com.example.myapp',
name: 'MyApp',
title: 'My Business App',
description: 'A comprehensive business management application',
subdomain: 'myapp',
capabilities: ['FRONTEND_WEBSITE', 'API_SERVICE'],
group: 'productivity',
version: '1.0.0',
build: 'abc123',
sdktype: 'JAVASCRIPT',
icon: 'business',
order: 1
}
});
// Boot the SDK to trigger registration
await sdk.boot();import { type ProductCapability } from '@asterisms/common-core';
const CAPABILITIES: ProductCapability[] = ['API_SERVICE', 'FRONTEND_WEBSITE', 'DASHBOARD_DATA'];
// Example usage
const registrationData = {
product: {
bundleId: 'com.example.myapp',
name: 'MyApp',
title: 'My Business App',
description: 'A comprehensive business management application',
subdomain: 'myapp',
capabilities: ['FRONTEND_WEBSITE', 'API_SERVICE'],
group: 'productivity',
version: '1.0.0',
build: 'abc123',
sdktype: 'JAVASCRIPT' as const
},
version: '1.0.0',
build: 'abc123',
sdktype: 'JAVASCRIPT' as const
};function getCurrentProductInfo() {
const information = sdk.information();
const currentProduct = information.currentProduct();
console.log('Current Product Information:', {
bundleId: currentProduct.bundleId,
name: currentProduct.name,
capabilities: currentProduct.capabilities,
version: currentProduct.version,
build: currentProduct.build,
subdomain: currentProduct.subdomain,
description: currentProduct.description
});
return currentProduct;
}// src/routes/api/registration/+server.ts
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export const GET: RequestHandler = async ({ locals }) => {
try {
const sdk = locals.sdk;
if (!sdk) {
return json({ error: 'SDK not available' }, { status: 503 });
}
const information = sdk.information();
const currentProduct = information.currentProduct();
const rootDomain = information.rootDomain();
return json({
product: currentProduct,
rootDomain,
success: true
});
} catch (error) {
console.error('Registration API error:', error);
return json({ error: 'Failed to get registration info' }, { status: 500 });
}
};// src/routes/api/registration/register/+server.ts
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { type ProductRegistrationData } from '@asterisms/common-core';
export const POST: RequestHandler = async ({ request, locals }) => {
try {
const sdk = locals.sdk;
if (!sdk) {
return json({ error: 'SDK not available' }, { status: 503 });
}
const registrationData: ProductRegistrationData = await request.json();
// Validate required fields
if (!registrationData.product?.bundleId || !registrationData.product?.name) {
return json({ error: 'BundleId and name are required' }, { status: 400 });
}
// Check authorization
const auth = sdk.authorization();
const currentUser = await auth.getCurrentUser();
if (!currentUser) {
return json({ error: 'Authentication required' }, { status: 401 });
}
const registration = sdk.registration();
const response = await registration.register(registrationData);
return json({
success: true,
entity: response.entity
});
} catch (error) {
console.error('Registration API error:', error);
return json({ error: 'Registration failed' }, { status: 500 });
}
};<!-- src/lib/components/AppRegistration.svelte -->
<script lang="ts">
import { onMount } from 'svelte';
interface ProductInfo {
bundleId: string;
name: string;
title: string;
description: string;
capabilities: string[];
version: string;
subdomain: string;
icon?: string;
group?: string;
}
let productInfo: ProductInfo | null = null;
let loading = true;
let error: string | null = null;
let rootDomain = '';
onMount(async () => {
await loadProductInfo();
});
async function loadProductInfo() {
try {
loading = true;
error = null;
const response = await fetch('/api/registration');
if (!response.ok) {
throw new Error('Failed to load product info');
}
const data = await response.json();
productInfo = data.product;
rootDomain = data.rootDomain;
} catch (err) {
error = err instanceof Error ? err.message : 'Unknown error';
} finally {
loading = false;
}
}
function getCapabilityDescription(capability: string): string {
const descriptions: Record<string, string> = {
'API_SERVICE': 'Provides API endpoints for other services',
'FRONTEND_WEBSITE': 'Serves web interface to users',
'DASHBOARD_DATA': 'Provides data for dashboard displays'
};
return descriptions[capability] || 'Unknown capability';
}
</script>
<div class="app-registration">
<h3>Application Registration</h3>
{#if loading}
<div class="loading">Loading product information...</div>
{:else if error}
<div class="error">Error: {error}</div>
<button on:click={loadProductInfo}>Retry</button>
{:else if productInfo}
<div class="product-info">
<div class="info-grid">
<div class="info-item">
<label>Bundle ID:</label>
<span>{productInfo.bundleId}</span>
</div>
<div class="info-item">
<label>Name:</label>
<span>{productInfo.name}</span>
</div>
<div class="info-item">
<label>Title:</label>
<span>{productInfo.title}</span>
</div>
<div class="info-item">
<label>Subdomain:</label>
<span>{productInfo.subdomain}.{rootDomain}</span>
</div>
<div class="info-item">
<label>Version:</label>
<span>{productInfo.version}</span>
</div>
<div class="info-item">
<label>Group:</label>
<span>{productInfo.group || 'None'}</span>
</div>
</div>
<div class="capabilities">
<h4>Capabilities</h4>
<div class="capability-list">
{#each productInfo.capabilities as capability}
<div class="capability-item">
<span class="capability-name">{capability}</span>
<span class="capability-description">{getCapabilityDescription(capability)}</span>
</div>
{/each}
</div>
</div>
<div class="description">
<h4>Description</h4>
<p>{productInfo.description}</p>
</div>
</div>
{/if}
</div>
<style>
.app-registration {
background: white;
padding: 20px;
border-radius: 8px;
border: 1px solid #ddd;
max-width: 800px;
}
.loading, .error {
text-align: center;
padding: 20px;
}
.error {
color: #d32f2f;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.info-item {
display: flex;
flex-direction: column;
gap: 5px;
}
.info-item label {
font-weight: bold;
color: #555;
}
.info-item span {
font-family: monospace;
background: #f5f5f5;
padding: 4px 8px;
border-radius: 4px;
}
.capabilities {
margin-bottom: 20px;
}
.capability-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.capability-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
background: #f9f9f9;
border-radius: 4px;
}
.capability-name {
font-weight: bold;
color: #1976d2;
}
.capability-description {
color: #666;
font-size: 0.9em;
}
.description {
background: #f5f5f5;
padding: 15px;
border-radius: 4px;// routes/registration.js
const express = require('express');
const router = express.Router();
// Get product information
router.get('/registration', async (req, res) => {
try {
const sdk = req.sdk;
if (!sdk) {
return res.status(503).json({ error: 'SDK not available' });
}
// Get product information
const information = sdk.information();
const product = information.currentProduct();
const rootDomain = information.rootDomain();
res.json({
product,
rootDomain
});
} catch (error) {
console.error('Registration GET error:', error);
res.status(500).json({ error: 'Failed to get product info' });
}
});
// Register application
router.post('/registration', async (req, res) => {
try {
const sdk = req.sdk;
if (!sdk) {
return res.status(503).json({ error: 'SDK not available' });
}
const { bundleId, name, title, description, capabilities, version, subdomain, icon, group } =
req.body;
// Validate required fields
if (!bundleId || !name || !title || !description || !capabilities || !version || !subdomain) {
return res.status(400).json({ error: 'Missing required fields' });
}
// Create registration data
const registrationData = {
bundleId,
name,
title,
description,
capabilities,
version,
subdomain,
icon,
group
};
// Register the application
const registration = sdk.registration();
await registration.register(registrationData);
res.json({ success: true, message: 'Application registered successfully' });
} catch (error) {
console.error('Registration POST error:', error);
res.status(500).json({ error: 'Failed to register application' });
}
});
module.exports = router;// app.js
const express = require('express');
const registrationRoutes = require('./routes/registration');
const app = express();
// Middleware to attach SDK
app.use((req, res, next) => {
// Assuming SDK is available in some way
req.sdk = getSdkInstance();
next();
});
// Use registration routes
app.use('/api', registrationRoutes);
app.listen(3000, () => {
console.log('Server running on port 3000');
});// Error handling utility
export async function handleRegistrationErrors(operation: () => Promise<any>) {
try {
return await operation();
} catch (error) {
if (error instanceof Error) {
// Handle specific error types
if (error.message.includes('bundle ID already exists')) {
throw new Error('An application with this bundle ID is already registered');
}
if (error.message.includes('invalid subdomain')) {
throw new Error('The subdomain contains invalid characters or is reserved');
}
if (error.message.includes('capability not supported')) {
throw new Error('One or more capabilities are not supported');
}
if (error.message.includes('insufficient permissions')) {
throw new Error('You do not have permission to register applications');
}
}
// Default error
throw new Error('Registration failed. Please try again.');
}
}// Validation utilities
export function validateRegistrationData(data: ProductRegistrationData): string[] {
const errors: string[] = [];
// Bundle ID validation
if (!data.bundleId || !/^[a-zA-Z0-9._-]+$/.test(data.bundleId)) {
errors.push('Bundle ID must contain only letters, numbers, dots, hyphens, and underscores');
}
// Subdomain validation
if (!data.subdomain || !/^[a-zA-Z0-9-]+$/.test(data.subdomain)) {
errors.push('Subdomain must contain only letters, numbers, and hyphens');
}
// Capability validation
const validCapabilities = ['API_SERVICE', 'FRONTEND_WEBSITE', 'DASHBOARD_DATA'];
if (!data.capabilities || !Array.isArray(data.capabilities)) {
errors.push('Capabilities must be an array');
} else {
const invalidCapabilities = data.capabilities.filter((cap) => !validCapabilities.includes(cap));
if (invalidCapabilities.length > 0) {
errors.push(`Invalid capabilities: ${invalidCapabilities.join(', ')}`);
}
}
// Version validation
if (!data.version || !/^\d+\.\d+\.\d+/.test(data.version)) {
errors.push('Version must follow semantic versioning (e.g., 1.0.0)');
}
return errors;
}// Recommended registration flow
export async function registerApplication(sdk: BackendSDK, data: ProductRegistrationData) {
// 1. Validate data
const validationErrors = validateRegistrationData(data);
if (validationErrors.length > 0) {
throw new Error(`Validation failed: ${validationErrors.join(', ')}`);
}
// 2. Check if already registered
const information = sdk.information();
const currentProduct = information.currentProduct();
if (currentProduct && currentProduct.bundleId === data.bundleId) {
console.log('Application already registered with this bundle ID');
return currentProduct;
}
// 3. Register
const registration = sdk.registration();
await registration.register(data);
// 4. Verify registration
const updatedProduct = information.currentProduct();
if (!updatedProduct) {
throw new Error('Registration completed but product information not available');
}
return updatedProduct;
}// Environment-specific registration
export function getRegistrationConfig(
environment: 'dev' | 'staging' | 'prod'
): Partial<ProductRegistrationData> {
const baseConfig = {
name: 'My App',
title: 'My Application',
description: 'A sample application',
capabilities: ['API_SERVICE', 'FRONTEND_WEBSITE'] as ProductCapability[],
version: '1.0.0'
};
switch (environment) {
case 'dev':
return {
...baseConfig,
bundleId: 'com.example.myapp.dev',
subdomain: 'myapp-dev'
};
case 'staging':
return {
...baseConfig,
bundleId: 'com.example.myapp.staging',
subdomain: 'myapp-staging'
};
case 'prod':
return {
...baseConfig,
bundleId: 'com.example.myapp',
subdomain: 'myapp'
};
default:
throw new Error('Invalid environment');
}
}// Monitor registration status
export class RegistrationMonitor {
private sdk: BackendSDK;
private checkInterval: NodeJS.Timeout | null = null;
constructor(sdk: BackendSDK) {
this.sdk = sdk;
}
startMonitoring(intervalMs: number = 30000) {
this.checkInterval = setInterval(() => {
this.checkRegistrationStatus();
}, intervalMs);
}
stopMonitoring() {
if (this.checkInterval) {
clearInterval(this.checkInterval);
this.checkInterval = null;
}
}
private async checkRegistrationStatus() {
try {
const information = this.sdk.information();
const product = information.currentProduct();
if (!product) {
console.warn('Application is not registered');
return;
}
// Check if registration is still valid
const rootDomain = information.rootDomain();
console.log(`Application ${product.name} is registered on ${rootDomain}`);
} catch (error) {
console.error('Registration status check failed:', error);
}
}
}Registration Fails with "Bundle ID already exists"
Subdomain Validation Errors
Capability Validation Errors
Permission Errors
// Get debug information
export async function getRegistrationDebugInfo(sdk: BackendSDK) {
try {
const information = sdk.information();
const product = information.currentProduct();
const rootDomain = information.rootDomain();
return {
product,
rootDomain,
timestamp: new Date().toISOString()
};
} catch (error) {
return {
error: error instanceof Error ? error.message : 'Unknown error',
timestamp: new Date().toISOString()
};
}
}// src/routes/api/health/+server.ts
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export const GET: RequestHandler = async ({ locals }) => {
try {
const sdk = locals.sdk;
if (!sdk) {
return json(
{
status: 'unhealthy',
error: 'SDK not available'
},
{ status: 503 }
);
}
const registration = sdk.registration();
const appInfo = await registration.getAppInfo();
const health = {
status: 'healthy',
timestamp: new Date().toISOString(),
application: {
name: appInfo.name,
version: appInfo.version,
bundleId: appInfo.bundleId
},
sdk: {
booted: sdk.isBooted(),
version: '3.4.2' // From package.json
},
uptime: process.uptime()
};
return json(health);
} catch (error) {
console.error('Health check error:', error);
return json(
{
status: 'unhealthy',
error: 'Health check failed',
timestamp: new Date().toISOString()
},
{ status: 500 }
);
}
};class RegistrationMonitor {
private registration: RegistrationService;
private checkInterval: NodeJS.Timeout | null = null;
constructor(sdk: AsterismsBackendSDK) {
this.registration = sdk.registration();
}
start(intervalMs: number = 60000) {
this.checkInterval = setInterval(async () => {
try {
const appInfo = await this.registration.getAppInfo();
console.log('Registration status:', {
name: appInfo.name,
status: 'active',
lastCheck: new Date().toISOString()
});
} catch (error) {
console.error('Registration check failed:', error);
}
}, intervalMs);
}
stop() {
if (this.checkInterval) {
clearInterval(this.checkInterval);
this.checkInterval = null;
}
}
}
// Usage
const monitor = new RegistrationMonitor(sdk);
monitor.start(60000); // Check every minuteimport { AsterismsSDKError } from '@asterisms/sdk-backend';
try {
await registration.updateCapabilities(newCapabilities);
} catch (error) {
if (error instanceof AsterismsSDKError) {
switch (error.code) {
case 'REGISTRATION_NOT_FOUND':
console.error('Application not registered');
break;
case 'INVALID_CAPABILITIES':
console.error('Invalid capabilities provided');
break;
case 'PERMISSION_DENIED':
console.error('Permission denied to update registration');
break;
default:
console.error('Registration error:', error.message);
}
} else {
console.error('Unknown registration error:', error);
}
}async function updateCapabilitiesWithRetry(
capabilities: string[],
maxRetries: number = 3
): Promise<boolean> {
const registration = sdk.registration();
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
await registration.updateCapabilities(capabilities);
return true;
} catch (error) {
console.error(`Registration attempt ${attempt} failed:`, error);
if (attempt === maxRetries) {
return false;
}
// Wait before retry
await new Promise((resolve) => setTimeout(resolve, 2000 * attempt));
}
}
return false;
}import { describe, it, expect, beforeEach } from 'vitest';
import { getAsterismsBackendSDK } from '@asterisms/sdk-backend';
describe('Registration Service', () => {
let sdk: AsterismsBackendSDK;
let registration: RegistrationService;
beforeEach(async () => {
sdk = getAsterismsBackendSDK(testProps);
await sdk.boot();
registration = sdk.registration();
});
it('should get app info', async () => {
const appInfo = await registration.getAppInfo();
expect(appInfo).toBeDefined();
expect(appInfo.bundleId).toBe('com.example.test');
expect(appInfo.name).toBeDefined();
expect(appInfo.capabilities).toBeInstanceOf(Array);
});
it('should update capabilities', async () => {
const newCapabilities = ['FRONTEND_WEBSITE', 'BACKEND_SERVICE'];
await expect(registration.updateCapabilities(newCapabilities)).resolves.not.toThrow();
const appInfo = await registration.getAppInfo();
expect(appInfo.capabilities).toEqual(newCapabilities);
});
});import { test, expect } from '@playwright/test';
test('registration API works correctly', async ({ page }) => {
// Test getting registration info
const response = await page.request.get('/api/registration');
expect(response.ok()).toBeTruthy();
const data = await response.json();
expect(data.appInfo).toBeDefined();
expect(data.appInfo.bundleId).toBeDefined();
// Test updating capabilities
const updateResponse = await page.request.put('/api/registration', {
data: {
capabilities: ['FRONTEND_WEBSITE', 'BACKEND_SERVICE']
}
});
expect(updateResponse.ok()).toBeTruthy();
});// ✅ Good: Use reverse domain notation
const bundleId = 'com.company.product.service';
// ✅ Good: Be specific and descriptive
const bundleId = 'io.asterisms.workspace.frontend';
// ❌ Bad: Generic or unclear names
const bundleId = 'myapp';
const bundleId = 'service1';// ✅ Good: Use constants for capabilities
const CAPABILITIES = {
FRONTEND: 'FRONTEND_WEBSITE',
BACKEND: 'BACKEND_SERVICE',
WEBHOOKS: 'WEBHOOKS'
};
// ✅ Good: Validate capabilities before updating
function validateCapabilities(capabilities: string[]): boolean {
const validCapabilities = Object.values(CAPABILITIES);
return capabilities.every((cap) => validCapabilities.includes(cap));
}
// ❌ Bad: Hardcoded strings
await registration.updateCapabilities(['FRONTEND_WEBSITE']);// ✅ Good: Use semantic versioning
const registrationData = {
// ... other fields
version: '1.2.3',
metadata: {
buildDate: new Date().toISOString(),
commitHash: process.env.GIT_COMMIT,
environment: process.env.NODE_ENV
}
};// Monitor registration health
async function checkRegistrationHealth(): Promise<boolean> {
try {
const registration = sdk.registration();
const appInfo = await registration.getAppInfo();
// Check if registration is valid
if (!appInfo.bundleId || !appInfo.name) {
console.error('Invalid registration data');
return false;
}
return true;
} catch (error) {
console.error('Registration health check failed:', error);
return false;
}
}
// Set up periodic health checks
setInterval(async () => {
const isHealthy = await checkRegistrationHealth();
if (!isHealthy) {
// Send alert to monitoring system
console.error('Registration health check failed');
}
}, 60000); // Check every minute// Handle registration failures gracefully
async function initializeApp() {
try {
const sdk = getAsterismsBackendSDK(props);
await sdk.boot();
// Verify registration
const registration = sdk.registration();
const appInfo = await registration.getAppInfo();
console.log('App registered successfully:', appInfo.name);
} catch (error) {
console.error('Registration failed:', error);
// Continue with limited functionality
console.log('Starting in offline mode...');
}
}