feat(03-01): add Module SDK package and Prisma module registry schema
- Create @tessera/module-sdk with TesseraModule, ModuleRoute, ModuleManifest, ModuleCategory types - Add Module and TenantModuleActivation Prisma models with tenant-scoped unique constraint - Apply migration add-module-registry to PostgreSQL - Framework-agnostic ComponentType for lazy-loaded module UIs
This commit is contained in:
@@ -0,0 +1,38 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Module" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"slug" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"version" TEXT NOT NULL,
|
||||||
|
"category" TEXT NOT NULL,
|
||||||
|
"description" JSONB NOT NULL,
|
||||||
|
"icon" TEXT,
|
||||||
|
"isSystem" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Module_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "TenantModuleActivation" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"tenantId" TEXT NOT NULL,
|
||||||
|
"moduleId" TEXT NOT NULL,
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"activatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "TenantModuleActivation_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Module_slug_key" ON "Module"("slug");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "TenantModuleActivation_tenantId_idx" ON "TenantModuleActivation"("tenantId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "TenantModuleActivation_tenantId_moduleId_key" ON "TenantModuleActivation"("tenantId", "moduleId");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "TenantModuleActivation" ADD CONSTRAINT "TenantModuleActivation_moduleId_fkey" FOREIGN KEY ("moduleId") REFERENCES "Module"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
@@ -84,3 +84,29 @@ model LdapFieldMapping {
|
|||||||
|
|
||||||
@@unique([ldapConfigId, ldapField])
|
@@unique([ldapConfigId, ldapField])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model Module {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
slug String @unique
|
||||||
|
name String
|
||||||
|
version String
|
||||||
|
category String
|
||||||
|
description Json
|
||||||
|
icon String?
|
||||||
|
isSystem Boolean @default(false)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
activations TenantModuleActivation[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model TenantModuleActivation {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
tenantId String
|
||||||
|
moduleId String
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
activatedAt DateTime @default(now())
|
||||||
|
module Module @relation(fields: [moduleId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@unique([tenantId, moduleId])
|
||||||
|
@@index([tenantId])
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "@tessera/module-sdk",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"types": "src/index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"type-check": "tsc --noEmit"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^5.5.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* @tessera/module-sdk
|
||||||
|
*
|
||||||
|
* Public API for the Tessera module system.
|
||||||
|
* All module implementations should import types from this package.
|
||||||
|
*/
|
||||||
|
export type {
|
||||||
|
ComponentType,
|
||||||
|
ModuleCategory,
|
||||||
|
HttpMethod,
|
||||||
|
ModuleRoute,
|
||||||
|
TesseraModule,
|
||||||
|
ModuleManifest,
|
||||||
|
} from './types';
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/**
|
||||||
|
* @tessera/module-sdk
|
||||||
|
*
|
||||||
|
* Defines the contract for Tessera modules.
|
||||||
|
* All modules must implement the TesseraModule interface.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic component type for lazy-loaded module UIs.
|
||||||
|
* Framework-agnostic — maps to React.ComponentType when consumed by the frontend.
|
||||||
|
*/
|
||||||
|
// biome-ignore lint: generic component type for framework independence
|
||||||
|
export type ComponentType = (props: any) => any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Category classification for modules.
|
||||||
|
* Modules are grouped by category in the marketplace and sidebar.
|
||||||
|
*/
|
||||||
|
export type ModuleCategory =
|
||||||
|
| 'infrastructure'
|
||||||
|
| 'monitoring'
|
||||||
|
| 'development'
|
||||||
|
| 'security'
|
||||||
|
| 'communication'
|
||||||
|
| 'workflow'
|
||||||
|
| 'analytics'
|
||||||
|
| 'integration'
|
||||||
|
| string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP method for module-defined API routes.
|
||||||
|
*/
|
||||||
|
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a single API route that a module exposes.
|
||||||
|
*/
|
||||||
|
export interface ModuleRoute {
|
||||||
|
/** HTTP method */
|
||||||
|
method: HttpMethod;
|
||||||
|
/** Route path relative to the module's base path (e.g., '/check') */
|
||||||
|
path: string;
|
||||||
|
/** Handler identifier (maps to a controller method) */
|
||||||
|
handler: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Full module definition including frontend components.
|
||||||
|
* Used by the frontend to render module UIs.
|
||||||
|
*/
|
||||||
|
export interface TesseraModule {
|
||||||
|
/** Unique module identifier (UUID) */
|
||||||
|
id: string;
|
||||||
|
/** Human-readable module name */
|
||||||
|
name: string;
|
||||||
|
/** Semantic version string */
|
||||||
|
version: string;
|
||||||
|
/** URL-safe unique slug (e.g., 'domaincheck') */
|
||||||
|
slug: string;
|
||||||
|
/** Module category for grouping */
|
||||||
|
category: ModuleCategory;
|
||||||
|
/** i18n descriptions keyed by locale (e.g., { de: '...', en: '...' }) */
|
||||||
|
description: Record<string, string>;
|
||||||
|
/** Optional icon identifier (e.g., 'Globe', 'Shield') */
|
||||||
|
icon?: string;
|
||||||
|
/** Optional API routes the module exposes */
|
||||||
|
routes?: ModuleRoute[];
|
||||||
|
/** Lazy-loaded main component for the module page */
|
||||||
|
component: () => Promise<{ default: ComponentType }>;
|
||||||
|
/** Optional lazy-loaded card component for the dashboard */
|
||||||
|
cardComponent?: () => Promise<{ default: ComponentType }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backend-only module metadata (no frontend component references).
|
||||||
|
* Used for module registration and API-side operations.
|
||||||
|
*/
|
||||||
|
export type ModuleManifest = Omit<TesseraModule, 'component' | 'cardComponent'>;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src"
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
Generated
+6
@@ -161,6 +161,12 @@ importers:
|
|||||||
specifier: ^5.5.0
|
specifier: ^5.5.0
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
|
|
||||||
|
packages/module-sdk:
|
||||||
|
devDependencies:
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.5.0
|
||||||
|
version: 5.9.3
|
||||||
|
|
||||||
packages/shared:
|
packages/shared:
|
||||||
devDependencies:
|
devDependencies:
|
||||||
typescript:
|
typescript:
|
||||||
|
|||||||
Reference in New Issue
Block a user