Skip to main content

Devlog Feb 12: Monorepo & Package Structure

· 9 min read
Eduardez
MoLOS Lead Developer

This week we restructured the MoLOS codebase into a Turborepo monorepo, extracting shared functionality into scoped packages for better maintainability and code reuse.

Turborepo Setup

Repository Structure

The monorepo structure follows Turborepo best practices:

MoLOS/
├── apps/
│ └── web/ # Main SvelteKit application
├── packages/
│ ├── @molos/core/ # Core utilities and types
│ ├── @molos/ui/ # Shared UI components
│ └── @molos/database/ # Database schemas and utilities
├── package.json
├── turbo.json
└── pnpm-workspace.yaml

Turborepo Configuration

{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**", "dist/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"lint": {
"outputs": []
},
"typecheck": {
"dependsOn": ["^build"],
"outputs": []
},
"test": {
"dependsOn": ["^build"],
"outputs": ["coverage/**"]
}
}
}

Workspace Configuration

# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'

@molos/core Package

Package Structure

packages/@molos/core/
├── src/
│ ├── types/
│ │ ├── index.ts
│ │ ├── ai.ts
│ │ └── module.ts
│ ├── utils/
│ │ ├── index.ts
│ │ ├── crypto.ts
│ │ └── validation.ts
│ └── index.ts
├── package.json
└── tsconfig.json

Common Types

Core package provides shared TypeScript types:

// src/types/ai.ts
export interface Message {
id: string;
role: 'user' | 'assistant' | 'system' | 'tool';
content: string;
createdAt: Date;
}

export interface Tool {
name: string;
description: string;
parameters: Record<string, ToolParameter>;
}

export interface ToolParameter {
type: 'string' | 'number' | 'boolean' | 'array' | 'object';
description: string;
required: boolean;
default?: unknown;
}

// src/types/module.ts
export interface Module {
id: string;
name: string;
version: string;
description: string;
enabled: boolean;
tools?: Tool[];
configSchema?: unknown;
createdAt: Date;
}

export interface ModuleConfig {
[key: string]: unknown;
}

Utility Functions

Common utilities shared across packages:

// src/utils/crypto.ts
import crypto from 'crypto';

export function generateId(): string {
return crypto.randomUUID();
}

export function generateApiKey(): string {
const prefix = 'mols_';
const secret = crypto.randomBytes(32).toString('hex');
return `${prefix}${secret}`;
}

export async function hashApiKey(apiKey: string): Promise<string> {
return crypto
.createHash('sha256')
.update(apiKey)
.digest('hex');
}

// src/utils/validation.ts
import { z } from 'zod';

export function validateConfig<T>(
schema: z.ZodSchema<T>,
config: unknown
): T {
return schema.parse(config);
}

export function createConfigValidator<T>(
schema: z.ZodSchema<T>
): (config: unknown) => T {
return (config: unknown) => validateConfig(schema, config);
}

Package Configuration

{
"name": "@molos/core",
"version": "1.0.0",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": "./src/index.ts",
"./types": "./src/types/index.ts",
"./utils": "./src/utils/index.ts"
},
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"zod": "^3.22.0"
},
"devDependencies": {
"tsup": "^8.0.0",
"typescript": "^5.3.0"
}
}

@molos/ui Package

Package Structure

packages/@molos/ui/
├── src/
│ ├── components/
│ │ ├── Button/
│ │ │ ├── Button.svelte
│ │ │ └── index.ts
│ │ ├── Dialog/
│ │ │ ├── Dialog.svelte
│ │ │ └── index.ts
│ │ ├── TextField/
│ │ │ ├── TextField.svelte
│ │ │ └── index.ts
│ │ └── index.ts
│ ├── styles/
│ │ ├── tokens.css
│ │ └── global.css
│ └── index.ts
├── package.json
└── svelte.config.js

Component Examples

Shared UI components following Material Design 3:

<!-- src/components/Button/Button.svelte -->
<script lang="ts">
import type { Snippet } from 'svelte';

export let variant: 'filled' | 'outlined' | 'text' = 'filled';
export let size: 'small' | 'medium' | 'large' = 'medium';
export let disabled = false;
export let children: Snippet;
</script>

<button
class="button {variant} {size}"
{disabled}
>
{@render children()}
</button>

<style>
.button {
border-radius: 20px;
border: none;
cursor: pointer;
font-family: inherit;
font-weight: 500;
transition: all 0.2s ease;
}

.filled {
background-color: var(--md-sys-color-primary);
color: var(--md-sys-color-on-primary);
}

.outlined {
background-color: transparent;
border: 1px solid var(--md-sys-color-outline);
color: var(--md-sys-color-primary);
}

.small {
padding: 8px 16px;
font-size: 14px;
}

.medium {
padding: 10px 24px;
font-size: 16px;
}

.large {
padding: 12px 28px;
font-size: 18px;
}

.button:disabled {
opacity: 0.38;
cursor: not-allowed;
}
</style>

Design Tokens

Material Design 3 design tokens:

/* src/styles/tokens.css */
:root {
/* Color tokens */
--md-sys-color-primary: #6750A4;
--md-sys-color-on-primary: #FFFFFF;
--md-sys-color-primary-container: #EADDFF;
--md-sys-color-on-primary-container: #21005D;
--md-sys-color-secondary: #625B71;
--md-sys-color-on-secondary: #FFFFFF;
--md-sys-color-secondary-container: #E8DEF8;
--md-sys-color-on-secondary-container: #1D192B;
--md-sys-color-tertiary: #7D5260;
--md-sys-color-on-tertiary: #FFFFFF;
--md-sys-color-tertiary-container: #FFD8E4;
--md-sys-color-on-tertiary-container: #31111D;
--md-sys-color-error: #B3261E;
--md-sys-color-on-error: #FFFFFF;
--md-sys-color-error-container: #F9DEDC;
--md-sys-color-on-error-container: #410E0B;

/* Typography tokens */
--md-sys-typescale-display-large-font-size: 57px;
--md-sys-typescale-display-large-line-height: 64px;
--md-sys-typescale-display-large-font-weight: 400;

--md-sys-typescale-headline-medium-font-size: 28px;
--md-sys-typescale-headline-medium-line-height: 36px;
--md-sys-typescale-headline-medium-font-weight: 400;

--md-sys-typescale-title-medium-font-size: 16px;
--md-sys-typescale-title-medium-line-height: 24px;
--md-sys-typescale-title-medium-font-weight: 500;

/* Spacing tokens */
--md-sys-spacing-xs: 4px;
--md-sys-spacing-sm: 8px;
--md-sys-spacing-md: 16px;
--md-sys-spacing-lg: 24px;
--md-sys-spacing-xl: 32px;

/* Radius tokens */
--md-sys-shape-corner-small: 8px;
--md-sys-shape-corner-medium: 12px;
--md-sys-shape-corner-large: 16px;
--md-sys-shape-corner-extra-large: 28px;
}

Package Configuration

{
"name": "@molos/ui",
"version": "1.0.0",
"type": "module",
"svelte": "./src/index.ts",
"exports": {
".": "./src/index.ts",
"./components": "./src/components/index.ts",
"./styles/tokens.css": "./src/styles/tokens.css"
},
"scripts": {
"build": "svelte-package",
"dev": "svelte-package --watch",
"typecheck": "svelte-check --tsconfig ./tsconfig.json"
},
"peerDependencies": {
"svelte": "^5.0.0"
},
"devDependencies": {
"@sveltejs/package": "^2.0.0",
"svelte": "^5.0.0",
"svelte-check": "^3.6.0",
"typescript": "^5.3.0"
}
}

@molos/database Package

Package Structure

packages/@molos/database/
├── src/
│ ├── schema/
│ │ ├── index.ts
│ │ ├── modules.ts
│ │ ├── users.ts
│ │ ├── ai.ts
│ │ ├── oauth.ts
│ │ └── mcp.ts
│ ├── migrations/
│ │ ├── 001_initial.sql
│ │ ├── 002_oauth.sql
│ │ └── 003_mcp.sql
│ └── index.ts
├── package.json
└── drizzle.config.ts

Database Schemas

Module schemas with namespacing:

// src/schema/modules.ts
import { pgTable, text, timestamp, boolean, jsonb } from 'drizzle-orm/pg-core';

export const modules = pgTable('modules', {
id: text('id').primaryKey(),
name: text('name').notNull(),
version: text('version').notNull(),
description: text('description'),
enabled: boolean('enabled').default(true).notNull(),
config: jsonb('config').$type<Record<string, unknown>>(),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull()
});

export const moduleTools = pgTable('module_tools', {
id: text('id').primaryKey(),
moduleId: text('module_id').references(() => modules.id).notNull(),
name: text('name').notNull(),
description: text('description').notNull(),
parameters: jsonb('parameters').$type<Record<string, unknown>>().notNull(),
createdAt: timestamp('created_at').defaultNow().notNull()
});

// src/schema/oauth.ts
export const oauthClients = pgTable('oauth_clients', {
id: text('id').primaryKey(),
name: text('name').notNull(),
secret: text('secret'),
redirectUris: text('redirect_uris').array().notNull(),
scopes: text('scopes').array().notNull(),
createdAt: timestamp('created_at').defaultNow().notNull()
});

export const oauthTokens = pgTable('oauth_tokens', {
id: text('id').primaryKey(),
clientId: text('client_id').references(() => oauthClients.id).notNull(),
accessToken: text('access_token').notNull(),
refreshToken: text('refresh_token'),
expiresAt: timestamp('expires_at').notNull(),
scopes: text('scopes').array().notNull(),
createdAt: timestamp('created_at').defaultNow().notNull()
});

// src/schema/mcp.ts
export const mcpResources = pgTable('mcp_resources', {
id: text('id').primaryKey(),
name: text('name').notNull(),
uri: text('uri').notNull(),
type: text('type').$type<'database' | 'api' | 'file' | 'url'>().notNull(),
mimeType: text('mime_type').notNull(),
config: jsonb('config').$type<Record<string, unknown>>(),
scopes: text('scopes').array().notNull(),
createdAt: timestamp('created_at').defaultNow().notNull()
});

export const mcpPrompts = pgTable('mcp_prompts', {
id: text('id').primaryKey(),
name: text('name').notNull(),
description: text('description').notNull(),
template: text('template').notNull(),
parameters: jsonb('parameters').array().notNull(),
createdAt: timestamp('created_at').defaultNow().notNull()
});

export const mcpApiKeys = pgTable('mcp_api_keys', {
id: text('id').primaryKey(),
name: text('name').notNull(),
keyHash: text('key_hash').notNull(),
scopes: text('scopes').array().notNull(),
modules: text('modules').array().notNull(),
expiresAt: timestamp('expires_at'),
createdAt: timestamp('created_at').defaultNow().notNull()
});

Migrations

Database migrations with proper tracking:

-- src/migrations/002_oauth.sql
CREATE TABLE IF NOT EXISTS oauth_clients (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
secret TEXT,
redirect_uris TEXT[] NOT NULL,
scopes TEXT[] NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS oauth_tokens (
id TEXT PRIMARY KEY,
client_id TEXT NOT NULL REFERENCES oauth_clients(id) ON DELETE CASCADE,
access_token TEXT NOT NULL,
refresh_token TEXT,
expires_at TIMESTAMP NOT NULL,
scopes TEXT[] NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX IF NOT EXISTS idx_oauth_tokens_client_id ON oauth_tokens(client_id);
CREATE INDEX IF NOT EXISTS idx_oauth_tokens_access_token ON oauth_tokens(access_token);

-- src/migrations/003_mcp.sql
CREATE TABLE IF NOT EXISTS mcp_resources (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
uri TEXT NOT NULL,
type TEXT NOT NULL CHECK (type IN ('database', 'api', 'file', 'url')),
mime_type TEXT NOT NULL,
config JSONB,
scopes TEXT[] NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS mcp_prompts (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT NOT NULL,
template TEXT NOT NULL,
parameters JSONB[] NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS mcp_api_keys (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
key_hash TEXT NOT NULL,
scopes TEXT[] NOT NULL,
modules TEXT[] NOT NULL,
expires_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX IF NOT EXISTS idx_mcp_api_keys_key_hash ON mcp_api_keys(key_hash);

Package Configuration

{
"name": "@molos/database",
"version": "1.0.0",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": "./src/index.ts",
"./schema": "./src/schema/index.ts"
},
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"typecheck": "tsc --noEmit",
"db:push": "drizzle-kit push",
"db:generate": "drizzle-kit generate"
},
"dependencies": {
"drizzle-orm": "^0.29.0",
"postgres": "^3.4.0"
},
"devDependencies": {
"drizzle-kit": "^0.20.0",
"tsup": "^8.0.0",
"typescript": "^5.3.0"
}
}

Module Package Conversion

Module Package Format

External modules are converted to npm package format:

// module/package.json
{
"name": "@molos-module/example",
"version": "1.0.0",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"molos": {
"id": "example",
"name": "Example Module",
"version": "1.0.0",
"description": "An example module",
"tools": [
{
"name": "exampleTool",
"description": "An example tool",
"entry": "./dist/tools/exampleTool.js"
}
]
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch"
},
"peerDependencies": {
"@molos/core": "^1.0.0"
},
"devDependencies": {
"@molos/core": "^1.0.0",
"typescript": "^5.3.0"
}
}

Module Type Definitions

// src/index.ts
import type { Module, Tool } from '@molos/core';

export const metadata: Module = {
id: 'example',
name: 'Example Module',
version: '1.0.0',
description: 'An example module',
tools: [
{
name: 'exampleTool',
description: 'An example tool',
parameters: {
input: {
type: 'string',
description: 'The input text',
required: true
}
}
}
]
};

export async function exampleTool(
params: { input: string }
): Promise<{ result: string }> {
return {
result: `Processed: ${params.input}`
};
}

Module Aliases

Vite configuration for module resolution:

// vite.config.ts
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';

export default defineConfig({
plugins: [sveltekit()],
resolve: {
alias: {
'@molos/core': '../../packages/@molos/core/src/index.ts',
'@molos/ui': '../../packages/@molos/ui/src/index.ts',
'@molos/database': '../../packages/@molos/database/src/index.ts',
'@molos-module/*': '../../external_modules/@molos-module/*/src/index.ts'
}
}
});

View commits on GitHub

What's Next

Future monorepo improvements include:

  • Additional shared packages (@molos/auth, @molos/utils)
  • Enhanced build pipeline with parallel processing
  • Module registry and discovery service
  • Cross-package testing integration
  • Improved documentation and examples

This monorepo restructuring improves code sharing, reduces duplication, and makes it easier to maintain and extend the MoLOS ecosystem.