Skip to content

Commit 45dc94b

Browse files
Claudehotlong
andauthored
feat(multi-tenant): complete Phase 2 - Turso Platform API integration and schema initialization
## Phase 2 Implementation Complete This commit completes Phase 2 of the multi-tenant architecture implementation, adding production-ready Turso Platform API integration and tenant schema initialization. ### New Features **Turso Platform API Integration** - Created `TursoPlatformClient` for programmatic database management - Automated tenant database creation via Turso Platform API - Tenant-specific auth token generation - Support for database lifecycle (create, delete, get, list) - Dual-mode operation: Production (real API) vs Development (mock mode) **Global Control Plane** - System object: `sys_tenant_database` - Tenant database registry - System object: `sys_package_installation` - Package installation tracking - Control plane driver integration for persistent tenant records - UUID-based tenant database naming (immutable) **Schema Initialization** - Created `TenantSchemaInitializer` service - Tenant database schema initialization with metadata tables - Package schema installation per tenant - Package schema uninstallation support **Tenant Lifecycle Management** - Suspend tenant databases - Archive tenant databases (with optional platform deletion) - Restore suspended tenants - Lifecycle state management in control plane ### Files Added - `packages/services/service-tenant/src/turso-platform-client.ts` - `packages/services/service-tenant/src/tenant-schema-initializer.ts` - `packages/services/service-tenant/src/objects/sys-tenant-database.object.ts` - `packages/services/service-tenant/src/objects/sys-package-installation.object.ts` - `packages/services/service-tenant/src/objects/index.ts` - `packages/services/service-tenant/src/tenant-integration.test.ts` ### Files Updated - `packages/services/service-tenant/src/tenant-provisioning.ts` - Enhanced from skeleton to full implementation - `packages/services/service-tenant/src/tenant-plugin.ts` - Added system object registration - `packages/services/service-tenant/src/index.ts` - Added new module exports - `packages/services/service-tenant/README.md` - Added Phase 2 documentation and examples - `ROADMAP.md` - Marked Phase 2 as complete, updated Phase 3 status ### Testing - Integration tests for tenant provisioning (mock mode) - UUID-based database naming validation - Lifecycle operation tests (suspend, archive, restore) - Control plane driver integration tests ### Next Steps (Phase 3) Remaining items for production hardening: - Multi-region tenant migration - Tenant usage tracking and quota enforcement - Cross-tenant data sharing policies - Tenant-specific RBAC and permissions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 82a7dbb commit 45dc94b

11 files changed

Lines changed: 1273 additions & 69 deletions

ROADMAP.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -615,15 +615,15 @@ Objects now declare `namespace: 'sys'` and a short `name` (e.g., `name: 'user'`)
615615
- [x] TenantProvisioningService skeleton — Minimal prototype for tenant database provisioning (Turso Platform API integration pending)
616616
- [x] Multi-tenant router documentation updates — UUID naming conventions and examples
617617
- [x] Test coverage — TenantContextService identification and caching tests
618-
- [ ] **Phase 2: Turso Platform API Integration (v3.5)**🔴 Planned
619-
- [ ] Turso Platform API client implementation
620-
- [ ] Automated tenant database creation
621-
- [ ] Tenant-specific auth token generation
622-
- [ ] Global control plane database setup (sys_tenant_registry, sys_package_installation)
623-
- [ ] Tenant database schema initialization
624-
- [ ] Package installation per tenant
625-
- [ ] **Phase 3: Production Hardening (v4.0)**🔴 Planned
626-
- [ ] Tenant lifecycle management (suspend, archive, restore)
618+
- [x] **Phase 2: Turso Platform API Integration (v3.5)**✅ Complete (2026-04-17)
619+
- [x] Turso Platform API client implementation
620+
- [x] Automated tenant database creation
621+
- [x] Tenant-specific auth token generation
622+
- [x] Global control plane database setup (sys_tenant_registry, sys_package_installation)
623+
- [x] Tenant database schema initialization
624+
- [x] Package installation per tenant
625+
- [ ] **Phase 3: Production Hardening (v4.0)**🟡 Partially Complete
626+
- [x] Tenant lifecycle management (suspend, archive, restore)
627627
- [ ] Multi-region tenant migration
628628
- [ ] Tenant usage tracking and quota enforcement
629629
- [ ] Cross-tenant data sharing policies

packages/services/service-tenant/README.md

Lines changed: 102 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,29 @@ Multi-tenant context management and routing service for ObjectStack.
44

55
## Overview
66

7-
This service provides tenant identification and context resolution for multi-tenant ObjectStack deployments. It supports multiple identification strategies and manages tenant-specific database routing.
7+
This service provides comprehensive multi-tenant infrastructure for ObjectStack deployments, including:
8+
9+
- Tenant identification and context resolution
10+
- Turso Platform API integration for automated database provisioning
11+
- Tenant database schema initialization
12+
- Global control plane management
13+
- Package installation per tenant
814

915
## Features
1016

17+
### Phase 1 (Complete)
1118
- **Multiple Identification Sources**: Subdomain, custom domain, HTTP headers, JWT claims, session
1219
- **UUID-Based Tenant Naming**: Immutable tenant identifiers (not organization slugs)
1320
- **Tenant Context Caching**: Performance optimization for frequently accessed tenants
1421
- **Flexible Configuration**: Priority-based identification source ordering
1522

23+
### Phase 2 (Complete)
24+
- **Turso Platform API Integration**: Automated database creation via Turso Platform API
25+
- **Tenant-Specific Auth Tokens**: Secure, database-specific authentication
26+
- **Global Control Plane**: System objects for tenant registry and package installations
27+
- **Schema Initialization**: Automated tenant database schema setup
28+
- **Package Management**: Per-tenant package installation and schema migration
29+
1630
## Installation
1731

1832
```bash
@@ -21,28 +35,108 @@ pnpm add @objectstack/service-tenant
2135

2236
## Usage
2337

24-
### Basic Setup
38+
### Basic Setup (Tenant Routing)
2539

2640
```typescript
2741
import { createTenantPlugin } from '@objectstack/service-tenant';
2842
import { ObjectKernel } from '@objectstack/core';
2943

3044
const kernel = new ObjectKernel();
3145

32-
// Create tenant plugin
46+
// Create tenant plugin with routing configuration
3347
const tenantPlugin = createTenantPlugin({
34-
enabled: true,
35-
identificationSources: ['header', 'custom_domain', 'jwt_claim'],
36-
tenantHeaderName: 'X-Tenant-ID',
37-
customDomainMapping: {
38-
'app.acme.com': '550e8400-e29b-41d4-a716-446655440000',
48+
routing: {
49+
enabled: true,
50+
identificationSources: ['header', 'custom_domain', 'jwt_claim'],
51+
tenantHeaderName: 'X-Tenant-ID',
52+
customDomainMapping: {
53+
'app.acme.com': '550e8400-e29b-41d4-a716-446655440000',
54+
},
3955
},
56+
registerSystemObjects: true, // Register control plane objects
4057
});
4158

4259
await kernel.use(tenantPlugin);
4360
await kernel.bootstrap();
4461
```
4562

63+
### Tenant Provisioning
64+
65+
```typescript
66+
import {
67+
TenantProvisioningService,
68+
TursoPlatformClient,
69+
} from '@objectstack/service-tenant';
70+
71+
// Production mode: with Turso Platform API
72+
const provisioningService = new TenantProvisioningService({
73+
turso: {
74+
apiToken: process.env.TURSO_API_TOKEN!,
75+
organization: 'my-org',
76+
},
77+
controlPlaneDriver: globalDriver, // Global control plane driver
78+
defaultRegion: 'us-east-1',
79+
databaseGroup: 'production-tenants',
80+
});
81+
82+
// Provision a new tenant
83+
const result = await provisioningService.provisionTenant({
84+
organizationId: 'org-123',
85+
plan: 'pro',
86+
region: 'us-west-2',
87+
storageLimitMb: 5120,
88+
});
89+
90+
console.log('Tenant provisioned:', result.tenant);
91+
// {
92+
// id: '550e8400-e29b-41d4-a716-446655440000',
93+
// databaseUrl: 'libsql://550e8400-e29b-41d4-a716-446655440000.turso.io',
94+
// authToken: '<encrypted-token>',
95+
// status: 'active',
96+
// ...
97+
// }
98+
```
99+
100+
### Development Mode (No Turso API)
101+
102+
```typescript
103+
// Development/Mock mode: no Turso Platform API required
104+
const devService = new TenantProvisioningService({
105+
defaultRegion: 'us-east-1',
106+
});
107+
108+
const result = await devService.provisionTenant({
109+
organizationId: 'org-123',
110+
plan: 'free',
111+
});
112+
113+
// Returns mock tenant with warnings
114+
console.log(result.warnings);
115+
// ['Running in mock mode - Turso Platform API credentials not configured']
116+
```
117+
118+
### Schema Initialization
119+
120+
```typescript
121+
import { TenantSchemaInitializer } from '@objectstack/service-tenant';
122+
123+
const initializer = new TenantSchemaInitializer();
124+
125+
// Initialize tenant database with base schema
126+
await initializer.initializeTenantSchema(
127+
tenant.databaseUrl,
128+
tenant.authToken,
129+
baseObjects, // Optional: base objects to create
130+
);
131+
132+
// Install package schema
133+
await initializer.installPackageSchema(
134+
tenant.databaseUrl,
135+
tenant.authToken,
136+
packageObjects,
137+
);
138+
```
139+
46140
### Resolving Tenant Context
47141

48142
```typescript

packages/services/service-tenant/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@
33
export * from './tenant-context';
44
export * from './tenant-plugin';
55
export * from './tenant-provisioning';
6+
export * from './turso-platform-client';
7+
export * from './tenant-schema-initializer';
8+
export * from './objects';
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
export * from './sys-tenant-database.object';
4+
export * from './sys-package-installation.object';
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
import { ObjectSchema, Field } from '@objectstack/spec/data';
4+
5+
/**
6+
* sys_package_installation — Package Installation Registry
7+
*
8+
* Tracks which packages are installed in which tenant.
9+
* Stored in the global control plane database.
10+
*
11+
* @namespace sys
12+
*/
13+
export const SysPackageInstallation = ObjectSchema.create({
14+
namespace: 'sys',
15+
name: 'package_installation',
16+
label: 'Package Installation',
17+
pluralLabel: 'Package Installations',
18+
icon: 'package',
19+
isSystem: true,
20+
description: 'Per-tenant package installation registry',
21+
titleFormat: '{package_id} @ {tenant_id}',
22+
compactLayout: ['package_id', 'tenant_id', 'version', 'status'],
23+
24+
fields: {
25+
id: Field.text({
26+
label: 'Installation ID',
27+
required: true,
28+
readonly: true,
29+
description: 'UUID-based installation identifier',
30+
}),
31+
32+
created_at: Field.datetime({
33+
label: 'Created At',
34+
defaultValue: 'NOW()',
35+
readonly: true,
36+
}),
37+
38+
updated_at: Field.datetime({
39+
label: 'Updated At',
40+
defaultValue: 'NOW()',
41+
readonly: true,
42+
}),
43+
44+
tenant_id: Field.text({
45+
label: 'Tenant ID',
46+
required: true,
47+
description: 'Foreign key to tenant_database',
48+
}),
49+
50+
package_id: Field.text({
51+
label: 'Package ID',
52+
required: true,
53+
maxLength: 255,
54+
description: 'Package identifier (e.g., @objectstack/crm)',
55+
}),
56+
57+
version: Field.text({
58+
label: 'Version',
59+
required: true,
60+
maxLength: 50,
61+
description: 'Installed package version (semver)',
62+
}),
63+
64+
status: Field.picklist({
65+
label: 'Status',
66+
required: true,
67+
options: [
68+
{ value: 'installing', label: 'Installing' },
69+
{ value: 'active', label: 'Active' },
70+
{ value: 'disabled', label: 'Disabled' },
71+
{ value: 'uninstalling', label: 'Uninstalling' },
72+
{ value: 'failed', label: 'Failed' },
73+
],
74+
defaultValue: 'installing',
75+
}),
76+
77+
installed_at: Field.datetime({
78+
label: 'Installed At',
79+
required: true,
80+
defaultValue: 'NOW()',
81+
}),
82+
83+
installed_by: Field.text({
84+
label: 'Installed By',
85+
required: true,
86+
description: 'User ID who installed the package',
87+
}),
88+
89+
config: Field.textarea({
90+
label: 'Configuration',
91+
required: false,
92+
description: 'JSON-serialized package-specific configuration',
93+
}),
94+
},
95+
96+
indexes: [
97+
{ fields: ['tenant_id', 'package_id'], unique: true },
98+
{ fields: ['tenant_id'] },
99+
{ fields: ['package_id'] },
100+
{ fields: ['status'] },
101+
},
102+
103+
enable: {
104+
trackHistory: true,
105+
searchable: true,
106+
apiEnabled: true,
107+
apiMethods: ['get', 'list', 'create', 'update', 'delete'],
108+
trash: true,
109+
mru: true,
110+
},
111+
});

0 commit comments

Comments
 (0)