Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,28 @@ Objects now declare `namespace: 'sys'` and a short `name` (e.g., `name: 'user'`)
- [x] Metadata-driven deploy pipeline — `system/deploy-bundle.zod.ts`: `DeployBundleSchema`, `MigrationPlanSchema`, `DeployDiffSchema`; `contracts/deploy-pipeline-service.ts`: `IDeployPipelineService`
- [x] App marketplace installation protocol — `system/app-install.zod.ts`: `AppManifestSchema`, `AppInstallResultSchema`, `AppCompatibilityCheckSchema`; `contracts/app-lifecycle-service.ts`: `IAppLifecycleService`
- [ ] Cross-tenant data sharing policies
- [x] **Phase 1: Multi-Tenant Protocol & Minimal Prototype (v3.4)** — ✅ Complete (2026-04-17)
- [x] UUID-based tenant database naming (not org-slug, for immutability)
- [x] Tenant registry schema — `cloud/tenant.zod.ts`: `TenantDatabaseSchema`, `PackageInstallationSchema`, `TenantContextSchema`, `TenantRoutingConfigSchema`, `ProvisionTenantRequestSchema`, `ProvisionTenantResponseSchema`
- [x] `@objectstack/service-tenant` package — Tenant context service, multi-tenant router integration, UUID-based naming enforcement
- [x] Tenant identification strategies — Subdomain, custom domain, HTTP header, JWT claim, session
- [x] TenantContextService — Tenant context resolution with caching and multiple identification sources
- [x] TenantProvisioningService skeleton — Minimal prototype for tenant database provisioning (Turso Platform API integration pending)
- [x] Multi-tenant router documentation updates — UUID naming conventions and examples
- [x] Test coverage — TenantContextService identification and caching tests
- [x] **Phase 2: Turso Platform API Integration (v3.5)** — ✅ Complete (2026-04-17)
- [x] Turso Platform API client implementation
- [x] Automated tenant database creation
- [x] Tenant-specific auth token generation
- [x] Global control plane database setup (sys_tenant_registry, sys_package_installation)
- [x] Tenant database schema initialization
- [x] Package installation per tenant
- [ ] **Phase 3: Production Hardening (v4.0)** — 🟡 Partially Complete
- [x] Tenant lifecycle management (suspend, archive, restore)
- [ ] Multi-region tenant migration
- [ ] Tenant usage tracking and quota enforcement
- [ ] Cross-tenant data sharing policies
- [ ] Tenant-specific RBAC and permissions

### 6.3 Observability

Expand Down
1 change: 1 addition & 0 deletions content/docs/references/cloud/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ This section contains all protocol schemas for the cloud layer of ObjectStack.
<Card href="/docs/references/cloud/developer-portal" title="Developer Portal" description="Source: packages/spec/src/cloud/developer-portal.zod.ts" />
<Card href="/docs/references/cloud/marketplace" title="Marketplace" description="Source: packages/spec/src/cloud/marketplace.zod.ts" />
<Card href="/docs/references/cloud/marketplace-admin" title="Marketplace Admin" description="Source: packages/spec/src/cloud/marketplace-admin.zod.ts" />
<Card href="/docs/references/cloud/tenant" title="Tenant" description="Source: packages/spec/src/cloud/tenant.zod.ts" />
</Cards>
4 changes: 3 additions & 1 deletion content/docs/references/cloud/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"app-store",
"developer-portal",
"marketplace",
"marketplace-admin"
"marketplace-admin",
"provisioning",
"tenant"
]
}
36 changes: 36 additions & 0 deletions content/docs/references/cloud/provisioning.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
title: Provisioning
description: Provisioning protocol schemas
---

{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs go in content/docs/guides/. */}

<Callout type="info">
**Source:** `packages/spec/src/cloud/provisioning.zod.ts`
</Callout>

## TypeScript Usage

```typescript
import { TenantPlan } from '@objectstack/spec/cloud';
import type { TenantPlan } from '@objectstack/spec/cloud';

// Validate data
const result = TenantPlan.parse(data);
```

---

## TenantPlan

### Allowed Values

* `free`
* `starter`
* `pro`
* `enterprise`
* `custom`


---

182 changes: 182 additions & 0 deletions content/docs/references/cloud/tenant.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
---
title: Tenant
description: Tenant protocol schemas
---

{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs go in content/docs/guides/. */}

Multi-Tenant Architecture Schema

Defines the schema for managing multi-tenant architecture with:

- Global control plane: Single database for auth, org management, tenant registry

- Tenant data plane: Isolated databases per organization (UUID-based naming)

Design decisions:

- Database naming: \{uuid\}.turso.io (not org-slug, since slugs can be modified)

- Each tenant has its own Turso database for complete data isolation

- Global database stores user auth, organizations, and tenant metadata

<Callout type="info">
**Source:** `packages/spec/src/cloud/tenant.zod.ts`
</Callout>

## TypeScript Usage

```typescript
import { PackageInstallation, PackageInstallationStatus, ProvisionTenantRequest, ProvisionTenantResponse, TenantContext, TenantDatabase, TenantDatabaseStatus, TenantIdentificationSource, TenantRoutingConfig } from '@objectstack/spec/cloud';
import type { PackageInstallation, PackageInstallationStatus, ProvisionTenantRequest, ProvisionTenantResponse, TenantContext, TenantDatabase, TenantDatabaseStatus, TenantIdentificationSource, TenantRoutingConfig } from '@objectstack/spec/cloud';

// Validate data
const result = PackageInstallation.parse(data);
```

---

## PackageInstallation

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **id** | `string` | ✅ | Unique installation identifier |
| **tenantId** | `string` | ✅ | Tenant database ID |
| **packageId** | `string` | ✅ | Package identifier |
| **version** | `string` | ✅ | Installed package version |
| **status** | `Enum<'installing' \| 'active' \| 'disabled' \| 'uninstalling' \| 'failed'>` | ✅ | Installation status |
| **installedAt** | `string` | ✅ | Installation timestamp |
| **installedBy** | `string` | ✅ | User ID who installed the package |
| **config** | `Record<string, any>` | optional | Package-specific configuration |
| **updatedAt** | `string` | ✅ | Last update timestamp |


---

## PackageInstallationStatus

### Allowed Values

* `installing`
* `active`
* `disabled`
* `uninstalling`
* `failed`


---

## ProvisionTenantRequest

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **organizationId** | `string` | ✅ | Organization ID |
| **region** | `string` | optional | Deployment region preference |
| **plan** | `Enum<'free' \| 'starter' \| 'pro' \| 'enterprise' \| 'custom'>` | ✅ | Tenant plan tier |
| **storageLimitMb** | `integer` | optional | Storage limit in megabytes |
| **metadata** | `Record<string, any>` | optional | Custom tenant metadata |


---

## ProvisionTenantResponse

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **tenant** | `Object` | ✅ | Provisioned tenant database |
| **durationMs** | `number` | ✅ | Provisioning duration in milliseconds |
| **warnings** | `string[]` | optional | Provisioning warnings |


---

## TenantContext

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **tenantId** | `string` | ✅ | Current tenant database ID |
| **organizationId** | `string` | ✅ | Current organization ID |
| **organizationSlug** | `string` | optional | Organization slug |
| **databaseUrl** | `string` | ✅ | Tenant database URL |
| **plan** | `Enum<'free' \| 'starter' \| 'pro' \| 'enterprise' \| 'custom'>` | ✅ | Tenant plan tier |
| **metadata** | `Record<string, any>` | optional | Custom tenant metadata |


---

## TenantDatabase

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **id** | `string` | ✅ | Unique tenant database identifier (UUID) |
| **organizationId** | `string` | ✅ | Organization ID (foreign key to sys_organization) |
| **databaseName** | `string` | ✅ | Database name (UUID-based) |
| **databaseUrl** | `string` | ✅ | Full database URL |
| **authToken** | `string` | ✅ | Encrypted tenant-specific auth token |
| **status** | `Enum<'provisioning' \| 'active' \| 'suspended' \| 'archived' \| 'failed'>` | ✅ | Database status |
| **region** | `string` | ✅ | Deployment region |
| **plan** | `Enum<'free' \| 'starter' \| 'pro' \| 'enterprise' \| 'custom'>` | ✅ | Tenant plan tier |
| **storageLimitMb** | `integer` | ✅ | Storage limit in megabytes |
| **createdAt** | `string` | ✅ | Database creation timestamp |
| **updatedAt** | `string` | ✅ | Last update timestamp |
| **lastAccessedAt** | `string` | optional | Last accessed timestamp |
| **metadata** | `Record<string, any>` | optional | Custom tenant configuration |


---

## TenantDatabaseStatus

### Allowed Values

* `provisioning`
* `active`
* `suspended`
* `archived`
* `failed`


---

## TenantIdentificationSource

### Allowed Values

* `subdomain`
* `custom_domain`
* `header`
* `jwt_claim`
* `session`
* `default`


---

## TenantRoutingConfig

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **enabled** | `boolean` | ✅ | Enable multi-tenant mode |
| **identificationSources** | `Enum<'subdomain' \| 'custom_domain' \| 'header' \| 'jwt_claim' \| 'session' \| 'default'>[]` | ✅ | Tenant identification strategy (in order of precedence) |
| **defaultTenantId** | `string` | optional | Default tenant ID |
| **subdomainPattern** | `string` | optional | Subdomain pattern for tenant extraction |
| **customDomainMapping** | `Record<string, string>` | optional | Custom domain to tenant ID mapping |
| **tenantHeaderName** | `string` | ✅ | Header name for tenant ID |
| **jwtOrganizationClaim** | `string` | ✅ | JWT claim name for organization ID |


---

29 changes: 23 additions & 6 deletions packages/plugins/driver-turso/src/multi-tenant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,14 @@
*
* Manages per-tenant TursoDriver instances with TTL-based caching.
* Uses a URL template with `{tenant}` placeholder that is replaced
* with the tenantId at runtime.
* with the tenantId (UUID) at runtime.
*
* Serverless-safe: no global intervals, no leaked state. Expired
* **UUID-Based Tenant Naming:**
* - Tenant IDs are UUIDs, not organization slugs
* - Ensures database URLs remain stable even if organization names change
* - Example: `550e8400-e29b-41d4-a716-446655440000.turso.io`
*
* **Serverless-safe:** no global intervals, no leaked state. Expired
* entries are evicted lazily on next access.
*/

Expand All @@ -18,20 +23,32 @@ import { TursoDriver, type TursoDriverConfig } from './turso-driver.js';
/**
* Configuration for the multi-tenant router.
*
* **UUID-Based Tenant Naming:**
* The `{tenant}` placeholder is replaced with a UUID, not an organization slug.
* This ensures database URLs remain stable even if organization names change.
*
* @example
* ```typescript
* const router = createMultiTenantRouter({
* urlTemplate: 'file:./data/{tenant}.db',
* // UUID-based URL template
* urlTemplate: 'libsql://{tenant}.turso.io',
* groupAuthToken: process.env.TURSO_GROUP_TOKEN,
* clientCacheTTL: 300_000, // 5 minutes
* });
*
* const driver = await router.getDriverForTenant('acme');
* // Tenant ID is a UUID
* const driver = await router.getDriverForTenant('550e8400-e29b-41d4-a716-446655440000');
* ```
*/
export interface MultiTenantConfig {
/**
* URL template with `{tenant}` placeholder.
* Example: `'file:./data/{tenant}.db'`
* URL template with `{tenant}` placeholder (replaced with UUID at runtime).
*
* Examples:
* - Remote: `'libsql://{tenant}.turso.io'`
* - Local: `'file:./data/{tenant}.db'`
*
* **Important:** Use UUID for tenant ID, not organization slug.
*/
urlTemplate: string;

Expand Down
Loading
Loading