- Added @eslint/js dependency - Updated eslint.config.js for ESLint 9 compatibility - Added required globals (crypto, localStorage, React, etc.) - Fixed unused imports and variables - Disabled sort-imports temporarily - Formatted all files with Prettier
831 lines
39 KiB
Markdown
831 lines
39 KiB
Markdown
# ByteLyst Ecosystem — Post-Refactor Architecture
|
|
|
|
> **Companion diagram:** `ecosystem-after-refactor.drawio` (open in draw.io or VS Code draw.io extension)
|
|
>
|
|
> **Date:** 2026-02-12 · **Author:** Cascade
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [Ecosystem Overview](#1-ecosystem-overview)
|
|
2. [Three-Repo Structure](#2-three-repo-structure)
|
|
3. [Common Platform Packages — Detailed](#3-common-platform-packages--detailed)
|
|
4. [Component & Service Inventory](#4-component--service-inventory)
|
|
5. [Dependency Graph](#5-dependency-graph)
|
|
6. [Migration Plan](#6-migration-plan)
|
|
7. [Advantages](#7-advantages)
|
|
8. [Cautions & Risks](#8-cautions--risks)
|
|
9. [Versioning Strategy](#9-versioning-strategy)
|
|
10. [Testing Strategy](#10-testing-strategy)
|
|
11. [CI/CD Impact](#11-cicd-impact)
|
|
12. [Decision Log](#12-decision-log)
|
|
|
|
---
|
|
|
|
## 1. Ecosystem Overview
|
|
|
|
The ByteLyst ecosystem consists of **two product repos** and **one shared infrastructure repo**:
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
│ CLIENT APPLICATIONS │
|
|
│ LysnrAI Desktop · iOS · Android · Admin · User Portal · Tracker │
|
|
│ MindLyst iOS · Android · Web │
|
|
└───────────────────────────────┬──────────────────────────────────────────┘
|
|
│
|
|
┌──────────────────┐ ┌────────┴────────┐ ┌──────────────────────────────┐
|
|
│ LysnrAI Repo │ │ Common Platform │ │ MindLyst Repo │
|
|
│ (voice_ai_agent)│←─│ (common_plat) │─→│ (multimodal_memory_agents) │
|
|
│ │ │ @bytelyst/* │ │ │
|
|
│ 4 Fastify svcs │ │ 8 npm packages │ │ KMP shared module │
|
|
│ 3 Next.js apps │ │ │ │ 3 native apps │
|
|
│ 1 FastAPI │ │ Design tokens │ │ 1 Next.js web │
|
|
│ 1 Desktop app │ │ (JSON → all) │ │ design-system/ │
|
|
│ 2 Mobile apps │ │ │ │ │
|
|
└────────┬─────────┘ └─────────────────┘ └──────────────┬───────────────┘
|
|
│ │
|
|
┌────────┴─────────────────────────────────────────────────┴───────────────┐
|
|
│ AZURE CLOUD INFRASTRUCTURE │
|
|
│ Cosmos DB · Blob Storage · Key Vault · Speech · OpenAI · Stripe │
|
|
│ Docker Compose (Traefik · Loki · Grafana) · GitHub Actions │
|
|
└──────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Three-Repo Structure
|
|
|
|
### 2.1 learning_voice_ai_agent (LysnrAI)
|
|
|
|
**Purpose:** Cross-platform voice-to-text dictation platform.
|
|
|
|
| Component | Tech | Port | Description |
|
|
| --------------------- | -------------------- | ---- | ------------------------------------------------------ |
|
|
| Desktop App | Python 3.12, tkinter | — | macOS/Windows dictation app |
|
|
| FastAPI Backend | Python 3.12 | 8000 | REST API, cloud sync |
|
|
| platform-service | Fastify + TS | 4003 | Auth, audit, flags, notifications, blob, rate limiting |
|
|
| billing-service | Fastify + TS | 4002 | Subscriptions, plans, usage, licenses, Stripe |
|
|
| growth-service | Fastify + TS | 4001 | Invitations, referrals, promo codes |
|
|
| tracker-service | Fastify + TS | 4004 | Feature requests, bugs, votes, public roadmap |
|
|
| admin-dashboard-web | Next.js 16 | 3001 | Admin panel (users, tokens, audit, themes) |
|
|
| user-dashboard-web | Next.js 16 | 3002 | User portal (profile, billing, settings, SSO) |
|
|
| tracker-dashboard-web | Next.js | 3003 | Tracker board, kanban, public roadmap |
|
|
| iOS App | Swift + SwiftUI | — | Native (no KMP) |
|
|
| Android App | Kotlin + Compose | — | Native (no KMP) |
|
|
| Monitoring | Loki + Grafana | — | Health checks, logs, metrics |
|
|
|
|
### 2.2 learning_multimodal_memory_agents (MindLyst)
|
|
|
|
**Purpose:** Role-based Life OS — multimodal second-brain with AI triage.
|
|
|
|
| Component | Tech | Description |
|
|
| ----------------- | --------------------- | -------------------------------------------------------- |
|
|
| KMP Shared Module | Kotlin Multiplatform | Models, repositories, DI, triage pipeline, API clients |
|
|
| iOS App | SwiftUI + KMP | Native UI consuming shared Kotlin module |
|
|
| Android App | Jetpack Compose + KMP | Native UI consuming shared Kotlin module |
|
|
| Web Dashboard | Next.js 14 | Pages Router, CSS custom properties, landing + dashboard |
|
|
| design-system/ | JSON + CSS | Canonical tokens, component specs |
|
|
|
|
### 2.3 learning_ai_common_plat (Common Platform)
|
|
|
|
**Purpose:** Shared npm packages consumed by both product repos.
|
|
|
|
| Package | Type | Primary Consumers |
|
|
| ------------------------- | ------------------- | -------------------------------------------------------- |
|
|
| `@bytelyst/errors` | TypeScript | All 4 Fastify services, dashboards |
|
|
| `@bytelyst/cosmos` | TypeScript | All 4 Fastify services, 2 Next.js dashboards |
|
|
| `@bytelyst/config` | TypeScript | All 4 Fastify services |
|
|
| `@bytelyst/auth` | TypeScript | All services + dashboards with JWT |
|
|
| `@bytelyst/fastify-core` | TypeScript | All 4 Fastify services |
|
|
| `@bytelyst/api-client` | TypeScript | All 3 Next.js dashboards |
|
|
| `@bytelyst/react-auth` | TypeScript/React | All 3 Next.js dashboards |
|
|
| `@bytelyst/design-tokens` | JSON → multi-format | MindLyst (all platforms), potentially LysnrAI dashboards |
|
|
|
|
---
|
|
|
|
## 3. Common Platform Packages — Detailed
|
|
|
|
### 3.1 `@bytelyst/errors` (P0)
|
|
|
|
**What it replaces:** 4 separate `errors.ts` files across services (157 LOC total).
|
|
|
|
```typescript
|
|
// Unified error hierarchy
|
|
export class ServiceError extends Error {
|
|
constructor(public statusCode: number, message: string) { ... }
|
|
}
|
|
export class NotFoundError extends ServiceError { /* 404 */ }
|
|
export class BadRequestError extends ServiceError { /* 400 */ }
|
|
export class UnauthorizedError extends ServiceError { /* 401 */ }
|
|
export class ForbiddenError extends ServiceError { /* 403 */ }
|
|
export class ConflictError extends ServiceError { /* 409 */ }
|
|
export class TooManyRequestsError extends ServiceError { /* 429 */ }
|
|
```
|
|
|
|
**Design decision:** Superset of all error types currently used. Services import only what they need. The `details` property (used by billing-service's TooManyRequests) is added to the base class as optional.
|
|
|
|
**Dependencies:** None (leaf package).
|
|
|
|
---
|
|
|
|
### 3.2 `@bytelyst/cosmos` (P0)
|
|
|
|
**What it replaces:** 6 separate `cosmos.ts` files (306 LOC total).
|
|
|
|
```typescript
|
|
// Core exports
|
|
export function getCosmosClient(): CosmosClient;
|
|
export function getDatabase(dbName?: string): Database;
|
|
export function getContainer(name: string): Container;
|
|
|
|
// Container registry (dashboards use this)
|
|
export function registerContainers(config: Record<string, ContainerConfig>): void;
|
|
export function initializeAllContainers(): Promise<void>;
|
|
|
|
// Types
|
|
export interface ContainerConfig {
|
|
partitionKeyPath: string;
|
|
defaultTtl?: number | null;
|
|
}
|
|
```
|
|
|
|
**Design decision:** The simple `getContainer(name)` used by microservices works alongside the full registry used by dashboards. Services call `getContainer()` directly; dashboards call `registerContainers()` first to set up the partition key map.
|
|
|
|
**Dependencies:** `@azure/cosmos` (peer dependency).
|
|
|
|
---
|
|
|
|
### 3.3 `@bytelyst/config` (P1)
|
|
|
|
**What it replaces:** 4 separate `config.ts` + 4 separate `product-config.ts` (142 LOC total).
|
|
|
|
```typescript
|
|
// Base env schema — every Fastify service needs these
|
|
export const baseEnvSchema = z.object({
|
|
PORT: z.coerce.number().default(3000),
|
|
HOST: z.string().default('0.0.0.0'),
|
|
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
|
|
CORS_ORIGIN: z.string().optional(),
|
|
SERVICE_NAME: z.string(),
|
|
COSMOS_ENDPOINT: z.string().min(1),
|
|
COSMOS_KEY: z.string().min(1),
|
|
COSMOS_DATABASE: z.string().default('lysnrai'),
|
|
});
|
|
|
|
// Extend per-service
|
|
export function loadConfig<T extends z.ZodRawShape>(extension: T) {
|
|
return baseEnvSchema.extend(extension).parse(process.env);
|
|
}
|
|
|
|
// Product identity
|
|
export function loadProductIdentity(path?: string): ProductIdentity;
|
|
export interface ProductIdentity {
|
|
productId: string;
|
|
displayName: string;
|
|
licensePrefix: string;
|
|
configDirName: string;
|
|
envVarPrefix: string;
|
|
}
|
|
```
|
|
|
|
**Design decision:** Each service calls `loadConfig({ STRIPE_SECRET_KEY: z.string(), ... })` to extend the base. The product identity reads from `shared/product.json` or falls back to env vars, eliminating the hardcoded `product-config.ts` copies.
|
|
|
|
**Dependencies:** `zod` (peer dependency).
|
|
|
|
---
|
|
|
|
### 3.4 `@bytelyst/auth` (P1)
|
|
|
|
**What it replaces:** 5 separate JWT/auth files (250 LOC total).
|
|
|
|
```typescript
|
|
// JWT operations (configurable issuer + expiry)
|
|
export function createJwtUtils(options: {
|
|
issuer: string;
|
|
accessTokenExpiry?: string; // default "1h"
|
|
refreshTokenExpiry?: string; // default "30d"
|
|
}): {
|
|
createAccessToken(payload: TokenPayload): Promise<string>;
|
|
createRefreshToken(payload: { sub: string }): Promise<string>;
|
|
verifyToken(token: string): Promise<TokenPayload | null>;
|
|
};
|
|
|
|
// Fastify middleware
|
|
export function extractAuth(req: FastifyRequest): Promise<AuthPayload>;
|
|
export function requireRole(...roles: string[]): FastifyHook;
|
|
|
|
// Next.js server-side
|
|
export function getCurrentUser(
|
|
authHeader: string | null,
|
|
getUserById: Function
|
|
): Promise<UserDoc | null>;
|
|
|
|
// Password hashing
|
|
export function hashPassword(plain: string): Promise<string>;
|
|
export function verifyPassword(plain: string, hash: string): Promise<boolean>;
|
|
|
|
// Types
|
|
export interface AuthPayload {
|
|
sub: string;
|
|
email?: string;
|
|
role?: string;
|
|
productId?: string;
|
|
type?: string;
|
|
}
|
|
export interface TokenPayload {
|
|
sub: string;
|
|
email: string;
|
|
role: string;
|
|
type: 'access' | 'refresh';
|
|
}
|
|
```
|
|
|
|
**Design decision:** The `createJwtUtils()` factory pattern allows each service to configure its own issuer (`lysnrai-admin`, `lysnrai-user`, `lysnrai`) while sharing all the logic. Platform-service creates tokens; other services only verify. The `requireRole()` hook is a new addition that multiple services currently implement inline.
|
|
|
|
**Dependencies:** `jose`, `bcryptjs` (peer dependencies).
|
|
|
|
---
|
|
|
|
### 3.5 `@bytelyst/fastify-core` (P1)
|
|
|
|
**What it replaces:** 4 separate `server.ts` files (351 LOC total).
|
|
|
|
```typescript
|
|
export async function createServiceApp(options: {
|
|
name: string;
|
|
version: string;
|
|
description: string;
|
|
port?: number;
|
|
corsOrigin?: string;
|
|
}): Promise<FastifyInstance> {
|
|
const app = Fastify({ logger: true });
|
|
|
|
// CORS
|
|
await app.register(cors, { origin: ... });
|
|
|
|
// OpenAPI / Swagger
|
|
await app.register(swagger, { openapi: { info: { title, version, description } } });
|
|
|
|
// Prometheus metrics
|
|
await app.register(metricsPlugin, { endpoint: "/metrics" });
|
|
|
|
// x-request-id propagation
|
|
app.addHook("onRequest", requestIdHook);
|
|
|
|
// Health endpoint
|
|
app.get("/health", healthHandler(options.name, options.version));
|
|
|
|
// ServiceError-aware error handler
|
|
app.setErrorHandler(serviceErrorHandler);
|
|
|
|
return app;
|
|
}
|
|
|
|
export async function startService(app: FastifyInstance, port: number, host?: string): Promise<void>;
|
|
```
|
|
|
|
**Design decision:** After calling `createServiceApp()`, each service just registers its route modules and calls `startService()`. The server.ts in each service shrinks from ~85 lines to ~15 lines. The `healthHandler` returns the standard `{ status, service, version, timestamp, requestId }` shape that the monitoring health-check script expects.
|
|
|
|
**Dependencies:** `fastify`, `@fastify/cors`, `@fastify/swagger`, `fastify-metrics`, `@bytelyst/errors`, `@bytelyst/config` (peer dependencies).
|
|
|
|
---
|
|
|
|
### 3.6 `@bytelyst/api-client` (P2)
|
|
|
|
**What it replaces:** 5+ separate fetch wrapper implementations (200 LOC total).
|
|
|
|
```typescript
|
|
export function createApiClient(options: {
|
|
baseUrl: string;
|
|
getToken?: () => string | null;
|
|
defaultHeaders?: Record<string, string>;
|
|
}): {
|
|
// Throws on error (for service-to-service)
|
|
fetch<T>(path: string, options?: RequestInit): Promise<T>;
|
|
// Returns { data, error } tuple (for UI components)
|
|
safeFetch<T>(
|
|
path: string,
|
|
options?: RequestInit
|
|
): Promise<{ data: T | null; error: string | null }>;
|
|
};
|
|
```
|
|
|
|
**Design decision:** Two fetch modes: throwing (used by server-side service clients like `billing-client.ts`, `growth-client.ts`) and safe (used by React components via `api.ts`). The `getToken()` callback handles auth header injection without coupling to any specific storage mechanism.
|
|
|
|
**Dependencies:** None (uses native `fetch`).
|
|
|
|
---
|
|
|
|
### 3.7 `@bytelyst/react-auth` (P2)
|
|
|
|
**What it replaces:** 3 separate `auth-context.tsx` files (450 LOC total).
|
|
|
|
```typescript
|
|
export function createAuthProvider<TUser extends BaseUser>(config: {
|
|
storagePrefix: string; // "admin" | "portal" | "tracker"
|
|
loginEndpoint: string; // "/api/auth/login"
|
|
mapResponseToUser: (data: any) => TUser;
|
|
enableSSO?: boolean;
|
|
}): {
|
|
AuthProvider: React.FC<{ children: ReactNode }>;
|
|
useAuth: () => AuthContextValue<TUser>;
|
|
};
|
|
|
|
export interface BaseUser {
|
|
id?: string;
|
|
email: string;
|
|
name: string;
|
|
role: string;
|
|
}
|
|
|
|
export interface AuthContextValue<TUser> {
|
|
user: TUser | null;
|
|
isAuthenticated: boolean;
|
|
isLoading: boolean;
|
|
login: (email: string, password: string) => Promise<boolean>;
|
|
logout: () => void;
|
|
getAccessToken: () => string | null;
|
|
}
|
|
```
|
|
|
|
**Design decision:** Factory pattern produces a fully-typed `AuthProvider` + `useAuth()` pair. Each dashboard provides its own `TUser` type and storage prefix. SSO cookie support (used by user-dashboard) is opt-in via `enableSSO`. Registration support (also user-dashboard only) can be added as an optional config callback.
|
|
|
|
**Dependencies:** `react` (peer dependency), `@bytelyst/api-client`.
|
|
|
|
---
|
|
|
|
### 3.8 `@bytelyst/design-tokens` (P3)
|
|
|
|
**What it replaces:** 5 manually-synced token files across MindLyst.
|
|
|
|
```
|
|
packages/design-tokens/
|
|
├── tokens/
|
|
│ └── bytelyst.tokens.json ← SINGLE SOURCE OF TRUTH
|
|
├── generated/ ← Auto-generated (committed or gitignored)
|
|
│ ├── tokens.css ← CSS custom properties (--ml-*)
|
|
│ ├── tokens.ts ← TypeScript constants
|
|
│ ├── tokens.kt ← Kotlin object (MindLystTokens)
|
|
│ └── tokens.swift ← Swift structs (MindLystColors/Spacing/Radius)
|
|
├── scripts/
|
|
│ └── generate.ts ← Reads JSON, emits all formats
|
|
└── package.json
|
|
```
|
|
|
|
**Design decision:** The canonical `bytelyst.tokens.json` (migrated from MindLyst's existing `mindlyst.tokens.json`) is the single source. A generation script produces platform-specific files. Consumers either:
|
|
|
|
- **Import the generated file** directly (CSS, TS)
|
|
- **Copy the generated file** into their project as a build step (Kotlin, Swift)
|
|
|
|
This eliminates the manual sync problem where token changes require updating 5 files.
|
|
|
|
**Dependencies:** None at runtime; `typescript` for the generation script.
|
|
|
|
---
|
|
|
|
## 4. Component & Service Inventory
|
|
|
|
### 4.1 All Services (Post-Refactor)
|
|
|
|
| Service | Repo | Port | Shared Packages Used |
|
|
| ----------------- | -------- | ---- | ------------------------------------------ |
|
|
| platform-service | LysnrAI | 4003 | errors, cosmos, config, auth, fastify-core |
|
|
| billing-service | LysnrAI | 4002 | errors, cosmos, config, fastify-core |
|
|
| growth-service | LysnrAI | 4001 | errors, cosmos, config, fastify-core |
|
|
| tracker-service | LysnrAI | 4004 | errors, cosmos, config, auth, fastify-core |
|
|
| FastAPI Backend | LysnrAI | 8000 | (Python — separate concern) |
|
|
| admin-dashboard | LysnrAI | 3001 | cosmos, auth, api-client, react-auth |
|
|
| user-dashboard | LysnrAI | 3002 | cosmos, auth, api-client, react-auth |
|
|
| tracker-dashboard | LysnrAI | 3003 | api-client, react-auth |
|
|
| MindLyst Web | MindLyst | 3050 | design-tokens |
|
|
| MindLyst iOS | MindLyst | — | design-tokens (generated Swift) |
|
|
| MindLyst Android | MindLyst | — | design-tokens (generated Kotlin) |
|
|
|
|
### 4.2 Azure Infrastructure
|
|
|
|
| Service | Resource | Used By |
|
|
| ------------ | ----------------------------- | ---------------------------------- |
|
|
| Cosmos DB | cosmos-mywisprai (Serverless) | All TS services + dashboards |
|
|
| Blob Storage | bytelystblobs (6 containers) | platform-service, desktop, backend |
|
|
| Key Vault | kv-mywisprai | Desktop, mobile, backend |
|
|
| Speech | mywisprai-speech (F0) | Desktop, LysnrAI mobile |
|
|
| Azure OpenAI | mywisprai-openai-sweden (S0) | Desktop, backend |
|
|
| OpenAI API | api.openai.com | MindLyst KMP (triage, Whisper) |
|
|
| Stripe | Payments API | billing-service, growth-service |
|
|
|
|
### 4.3 DevOps Components
|
|
|
|
| Component | Location | Scope |
|
|
| ------------------------- | ---------------------------- | --------------------------------------- |
|
|
| Docker Compose | LysnrAI root | All services + Loki + Grafana + Traefik |
|
|
| Health Check | services/monitoring/ | Aggregates all /health endpoints |
|
|
| GitHub Actions | .github/workflows/ (9 files) | Per-service CI |
|
|
| run-local-all-services.sh | LysnrAI root | Dev startup script |
|
|
|
|
---
|
|
|
|
## 5. Dependency Graph
|
|
|
|
```
|
|
@bytelyst/design-tokens (standalone — no deps)
|
|
│
|
|
└─→ MindLyst iOS/Android/Web
|
|
|
|
@bytelyst/errors (standalone — no deps)
|
|
│
|
|
├─→ @bytelyst/fastify-core
|
|
├─→ All 4 Fastify services
|
|
└─→ All 3 Next.js dashboards (via API routes)
|
|
|
|
@bytelyst/cosmos (depends on: @azure/cosmos)
|
|
│
|
|
├─→ All 4 Fastify services
|
|
└─→ admin-dashboard, user-dashboard
|
|
|
|
@bytelyst/config (depends on: zod)
|
|
│
|
|
├─→ @bytelyst/fastify-core
|
|
├─→ @bytelyst/auth
|
|
└─→ All 4 Fastify services
|
|
|
|
@bytelyst/auth (depends on: jose, bcryptjs, @bytelyst/config)
|
|
│
|
|
├─→ @bytelyst/api-client
|
|
├─→ platform-service (creates tokens)
|
|
├─→ tracker-service (verifies tokens)
|
|
└─→ admin/user dashboards (server-side auth)
|
|
|
|
@bytelyst/fastify-core (depends on: fastify, @bytelyst/errors, @bytelyst/config)
|
|
│
|
|
└─→ All 4 Fastify services
|
|
|
|
@bytelyst/api-client (no runtime deps)
|
|
│
|
|
├─→ @bytelyst/react-auth
|
|
└─→ All 3 dashboards
|
|
|
|
@bytelyst/react-auth (depends on: react, @bytelyst/api-client)
|
|
│
|
|
└─→ All 3 Next.js dashboards
|
|
```
|
|
|
|
**Key insight:** The graph is a clean DAG (directed acyclic graph) with no circular dependencies. Leaf packages (`errors`, `cosmos`, `design-tokens`) can be extracted first with zero risk.
|
|
|
|
---
|
|
|
|
## 6. Migration Plan
|
|
|
|
### Phase 1: Foundation (Week 1)
|
|
|
|
1. **Initialize `learning_ai_common_plat`** as pnpm workspace monorepo
|
|
- `pnpm init`, `pnpm-workspace.yaml`, `tsconfig.base.json`
|
|
- Shared `vitest` config, `ruff.toml` (for future Python packages)
|
|
|
|
2. **Extract `@bytelyst/errors`** (P0, 1 hour)
|
|
- Copy `platform-service/src/lib/errors.ts` as starting point
|
|
- Add `ConflictError` (from tracker) and `TooManyRequestsError` (from billing)
|
|
- Add optional `details` field to base `ServiceError`
|
|
- Write tests (port from service tests)
|
|
- Update all 4 services: `import { ServiceError, ... } from "@bytelyst/errors"`
|
|
|
|
3. **Extract `@bytelyst/cosmos`** (P0, 2 hours)
|
|
- Merge service version (simple) + dashboard version (registry + TTL)
|
|
- Parameterize default database name
|
|
- Write tests (mock Cosmos client)
|
|
- Update all 6 consumers
|
|
|
|
### Phase 2: Service Infrastructure (Week 2)
|
|
|
|
4. **Extract `@bytelyst/config`** (P1, 2 hours)
|
|
- Create `baseEnvSchema` from common fields
|
|
- Add `loadConfig()` extension function
|
|
- Add `loadProductIdentity()` from `shared/product.json`
|
|
- Update all 4 services + remove 4 `product-config.ts` files
|
|
|
|
5. **Extract `@bytelyst/auth`** (P1, 3 hours)
|
|
- Create `createJwtUtils()` factory
|
|
- Port `extractAuth()` Fastify hook
|
|
- Port `getCurrentUser()` for Next.js
|
|
- Port `hashPassword()` / `verifyPassword()`
|
|
- Critical: test all auth flows end-to-end after migration
|
|
|
|
6. **Extract `@bytelyst/fastify-core`** (P1, 3 hours)
|
|
- Create `createServiceApp()` factory
|
|
- Port request-id hook, health handler, error handler
|
|
- Refactor all 4 `server.ts` files to use factory
|
|
- Verify Docker builds still pass
|
|
|
|
### Phase 3: Dashboard Libraries (Week 3)
|
|
|
|
7. **Extract `@bytelyst/api-client`** (P2, 2 hours)
|
|
- Create `createApiClient()` with both fetch modes
|
|
- Update dashboard client files to use shared client
|
|
- Service-specific methods stay in their respective client files
|
|
|
|
8. **Extract `@bytelyst/react-auth`** (P2, 3 hours)
|
|
- Create `createAuthProvider()` factory
|
|
- Port SSO cookie logic as opt-in
|
|
- Update all 3 dashboard auth contexts
|
|
- Test login/logout flows in each dashboard
|
|
|
|
### Phase 4: Design System (Week 4+)
|
|
|
|
9. **Extract `@bytelyst/design-tokens`** (P3, 4 hours)
|
|
- Migrate `mindlyst.tokens.json` as canonical source
|
|
- Write generation script for CSS, TS, Kotlin, Swift
|
|
- Add to MindLyst build pipeline
|
|
- Consider adopting for LysnrAI dashboards (future)
|
|
|
|
---
|
|
|
|
## 7. Advantages
|
|
|
|
### 7.1 Immediate Benefits
|
|
|
|
| Benefit | Impact | Measurement |
|
|
| --------------------------------- | ----------------------------------------------------- | --------------------------------- |
|
|
| **~1,580 LOC eliminated** | Less code to maintain | 8 fewer files per service feature |
|
|
| **Single-point security fixes** | JWT/auth vulnerability fixed once, applied everywhere | 1 PR instead of 5 |
|
|
| **Consistent error handling** | All services return identical error shapes | 0 divergence |
|
|
| **Faster new service creation** | `createServiceApp()` → 15 lines to launch | Minutes vs hours |
|
|
| **Cosmos DB config in one place** | Container registry + TTL managed centrally | 1 file vs 6 |
|
|
|
|
### 7.2 Medium-Term Benefits
|
|
|
|
| Benefit | Impact |
|
|
| ------------------------------ | -------------------------------------------------------------------------------- |
|
|
| **MindLyst backend bootstrap** | When MindLyst adds backend services, it gets Fastify infra for free |
|
|
| **Consistent design language** | Token generation ensures iOS/Android/Web never drift |
|
|
| **Cross-product features** | Tracker service already product-agnostic; shared auth enables MindLyst to use it |
|
|
| **Onboarding speed** | New developer learns one pattern, applies to all services |
|
|
| **Testability** | Shared packages have isolated tests; consumer tests focus on business logic |
|
|
|
|
### 7.3 Architectural Benefits
|
|
|
|
| Benefit | Description |
|
|
| -------------------------- | ---------------------------------------------------------------------- |
|
|
| **Separation of concerns** | Infrastructure (how) separated from business logic (what) |
|
|
| **Dependency inversion** | Services depend on abstractions (`@bytelyst/auth`) not implementations |
|
|
| **DRY principle** | No more copy-paste-modify cycle for new services |
|
|
| **Contract stability** | Shared types ensure API contracts don't drift between services |
|
|
| **Upgrade path** | Update `jose` or `@azure/cosmos` in one place |
|
|
|
|
---
|
|
|
|
## 8. Cautions & Risks
|
|
|
|
### 8.1 HIGH RISK — Tightly Coupled Releases
|
|
|
|
| Risk | Description | Mitigation |
|
|
| --------------------------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- |
|
|
| **Breaking change cascade** | A breaking change in `@bytelyst/auth` breaks all services simultaneously | Semantic versioning + `file:` references pin to exact commits. Run all consumer tests before publishing. |
|
|
| **Diamond dependency** | Two packages depend on different versions of a shared dep | Use peer dependencies + strict version ranges. `pnpm` handles this well. |
|
|
| **Deployment ordering** | Must deploy common-plat before consuming repos can use new features | Use git tags/releases. Consumers reference a specific version or commit. |
|
|
|
|
### 8.2 MEDIUM RISK — Development Friction
|
|
|
|
| Risk | Description | Mitigation |
|
|
| -------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
|
| **Cross-repo PRs** | A feature spanning common-plat + LysnrAI requires 2 PRs | Use `file:../learning_ai_common_plat/packages/X` in dev, pin to version in CI. |
|
|
| **Local dev setup** | Developers need all 3 repos cloned side-by-side | Document in README. Add a setup script. Consider git submodules as alternative. |
|
|
| **IDE navigation** | "Go to definition" may not work across repos | TypeScript project references or workspace-level `tsconfig.json` can help. |
|
|
| **Over-abstraction** | Temptation to extract things that aren't truly shared | Rule: **only extract when ≥2 consumers exist AND the code is >90% identical**. |
|
|
|
|
### 8.3 LOW RISK — Operational
|
|
|
|
| Risk | Description | Mitigation |
|
|
| --------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
| **npm registry dependency** | Publishing to GitHub Packages adds infra complexity | Start with `file:` references (zero infra). Upgrade to registry only if team grows. |
|
|
| **Test isolation** | Shared package tests may not catch consumer-specific issues | Each consumer repo keeps integration tests. Shared packages only unit-test their own API. |
|
|
| **Python exclusion** | The desktop app and FastAPI backend can't use TypeScript packages | Python remains independent. If patterns emerge (e.g., Cosmos client), create a Python equivalent later. |
|
|
| **KMP exclusion** | MindLyst's Kotlin code can't use npm packages directly | Only `design-tokens` applies to KMP (via generated Kotlin). All other packages are TS-only. |
|
|
|
|
### 8.4 Things to AVOID
|
|
|
|
1. **Don't extract too early** — Wait until code is stable and duplicated in ≥2 places
|
|
2. **Don't create a "utils" dumping ground** — Every package must have a clear, single responsibility
|
|
3. **Don't abstract configuration** — Each service's `config.ts` should remain in the service; only the base schema is shared
|
|
4. **Don't share React components** — UI components are product-specific; only infrastructure (auth, fetch) is shared
|
|
5. **Don't break existing imports in one big PR** — Migrate one package at a time, keep old imports working temporarily
|
|
|
|
---
|
|
|
|
## 9. Versioning Strategy
|
|
|
|
### Recommended: File References (Phase 1)
|
|
|
|
```jsonc
|
|
// In LysnrAI's services/platform-service/package.json
|
|
{
|
|
"dependencies": {
|
|
"@bytelyst/errors": "file:../../../learning_ai_common_plat/packages/errors",
|
|
"@bytelyst/cosmos": "file:../../../learning_ai_common_plat/packages/cosmos",
|
|
},
|
|
}
|
|
```
|
|
|
|
**Pros:** Zero infrastructure, works locally, `pnpm install` resolves it.
|
|
**Cons:** All repos must be cloned side-by-side. Docker builds need a multi-stage approach or volume mount.
|
|
|
|
### Future: GitHub Packages (Phase 2+)
|
|
|
|
```jsonc
|
|
{
|
|
"dependencies": {
|
|
"@bytelyst/errors": "^1.0.0",
|
|
"@bytelyst/cosmos": "^1.0.0",
|
|
},
|
|
}
|
|
```
|
|
|
|
**Pros:** True version pinning, CI-friendly, no local path dependency.
|
|
**Cons:** Requires GitHub Packages setup, publish workflow, access tokens.
|
|
|
|
### Versioning Rules
|
|
|
|
- **MAJOR** (1.0 → 2.0): Breaking change to public API (renamed export, removed function, changed return type)
|
|
- **MINOR** (1.0 → 1.1): New export, new optional parameter, new error subclass
|
|
- **PATCH** (1.0.0 → 1.0.1): Bug fix, internal refactor, dependency update
|
|
|
|
---
|
|
|
|
## 10. Testing Strategy
|
|
|
|
### Package-Level Tests
|
|
|
|
Each `@bytelyst/*` package has its own `vitest` test suite:
|
|
|
|
| Package | Test Focus |
|
|
| ------------- | ----------------------------------------------------------------------- |
|
|
| errors | Error class hierarchy, statusCode correctness, instanceof checks |
|
|
| cosmos | Client singleton behavior, container registry, env var reading (mocked) |
|
|
| config | Zod schema validation, defaults, extension merging |
|
|
| auth | JWT sign/verify round-trip, expiry, issuer validation, bcrypt hashing |
|
|
| fastify-core | App factory produces correct hooks/routes, health endpoint shape |
|
|
| api-client | Fetch wrapper behavior with mock server, error handling, auth injection |
|
|
| react-auth | Provider renders, login/logout flows, localStorage persistence |
|
|
| design-tokens | Generated output matches expected format for each platform |
|
|
|
|
### Consumer-Level Tests
|
|
|
|
Each service/dashboard keeps its existing test suite. After migration:
|
|
|
|
- Service tests verify that imported shared functions work correctly **in context**
|
|
- No need to re-test JWT internals — that's the package's job
|
|
- Focus on **integration**: "Does my route handler correctly call `extractAuth()` and get the right payload?"
|
|
|
|
### CI Matrix
|
|
|
|
```
|
|
common-plat CI:
|
|
→ Run all 8 package test suites
|
|
→ Run type-check (tsc --noEmit)
|
|
→ Run lint (ruff for future Python packages)
|
|
|
|
LysnrAI CI (per-service):
|
|
→ Install common-plat packages (file: or registry)
|
|
→ Run service tests
|
|
→ Run Next.js build
|
|
|
|
MindLyst CI:
|
|
→ Build KMP shared module
|
|
→ Build Next.js web
|
|
→ Verify generated tokens match source JSON
|
|
```
|
|
|
|
---
|
|
|
|
## 11. CI/CD Impact
|
|
|
|
### Docker Build Changes
|
|
|
|
Services currently build independently. After extraction:
|
|
|
|
**Option A: Multi-stage with copy** (recommended for `file:` references)
|
|
|
|
```dockerfile
|
|
# Copy common-plat packages into build context
|
|
COPY ../learning_ai_common_plat/packages/errors /common/errors
|
|
COPY ../learning_ai_common_plat/packages/cosmos /common/cosmos
|
|
# Adjust package.json to point to /common/*
|
|
```
|
|
|
|
**Option B: Pre-publish** (recommended for registry)
|
|
|
|
```dockerfile
|
|
# Common-plat packages are already on npm registry
|
|
RUN npm install # resolves @bytelyst/* from GitHub Packages
|
|
```
|
|
|
|
**Option C: Monorepo build context** (if repos are merged or submoduled)
|
|
|
|
```dockerfile
|
|
# Build context includes all repos
|
|
COPY . /workspace
|
|
WORKDIR /workspace/learning_voice_ai_agent/services/platform-service
|
|
RUN npm install && npm run build
|
|
```
|
|
|
|
### docker-compose.yml Changes
|
|
|
|
The existing `docker-compose.yml` may need an additional volume mount or build context adjustment:
|
|
|
|
```yaml
|
|
services:
|
|
platform-service:
|
|
build:
|
|
context: . # May need to widen to parent directory
|
|
dockerfile: services/platform-service/Dockerfile
|
|
```
|
|
|
|
### GitHub Actions Changes
|
|
|
|
- **New workflow:** `ci-common-plat.yml` — tests all shared packages on push to `learning_ai_common_plat`
|
|
- **Modified workflows:** Each service CI installs common-plat packages before running tests
|
|
- **Potential:** A "consumer test" job in `ci-common-plat.yml` that runs key consumer tests after package changes
|
|
|
|
---
|
|
|
|
## 12. Decision Log
|
|
|
|
| Decision | Rationale | Alternatives Considered |
|
|
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
|
|
| **pnpm workspace monorepo** for common-plat | Simplest multi-package management, strict dep resolution | npm workspaces (less strict), Turborepo (overkill for 8 packages), Nx (too heavy) |
|
|
| **`file:` references** initially | Zero infrastructure, works immediately | git submodules (complex merge), npm registry (needs setup), copy-paste (defeats purpose) |
|
|
| **Factory pattern** for fastify-core, auth, react-auth | Services need slightly different config (port, issuer, user type) but identical boilerplate | Inheritance (fragile), config objects (less type-safe), template codegen (complex) |
|
|
| **Peer dependencies** for heavy libs | Prevents version conflicts (e.g., two versions of `@azure/cosmos` in one service) | Direct deps (risk duplicate bundles), optional deps (too loose) |
|
|
| **TypeScript-only** packages (no Python) | Python desktop/backend have less duplication and different patterns | Shared Python package (only 2 consumers, not worth the packaging complexity yet) |
|
|
| **Design tokens as JSON → generated code** | JSON is the universal format; generation ensures consistency | Manual sync (error-prone), Figma API (adds dependency), CSS-only (excludes native) |
|
|
| **NOT extracting React UI components** | UI is product-specific; only infrastructure is truly shared | Shared component library (premature — products have different UX) |
|
|
| **NOT merging repos** | Three distinct products with different release cadences and teams | Monorepo (coupling), git submodules (merge conflicts) |
|
|
|
|
---
|
|
|
|
## Appendix A: File Counts Per Service (Before vs After)
|
|
|
|
### Before Refactor
|
|
|
|
```
|
|
services/platform-service/src/lib/
|
|
├── config.ts (32 lines)
|
|
├── cosmos.ts (25 lines)
|
|
├── errors.ts (38 lines)
|
|
├── product-config.ts (9 lines)
|
|
└── blob.ts (service-specific, stays)
|
|
|
|
services/billing-service/src/lib/
|
|
├── config.ts (34 lines) ← DUPLICATE
|
|
├── cosmos.ts (25 lines) ← DUPLICATE
|
|
├── errors.ts (41 lines) ← DUPLICATE
|
|
└── product-config.ts (9 lines) ← DUPLICATE
|
|
|
|
services/growth-service/src/lib/
|
|
├── config.ts (25 lines) ← DUPLICATE
|
|
├── cosmos.ts (25 lines) ← DUPLICATE
|
|
├── errors.ts (35 lines) ← DUPLICATE
|
|
└── product-config.ts (11 lines) ← DUPLICATE
|
|
|
|
services/tracker-service/src/lib/
|
|
├── auth.ts (52 lines) ← DUPLICATE
|
|
├── config.ts (24 lines) ← DUPLICATE
|
|
├── cosmos.ts (25 lines) ← DUPLICATE
|
|
├── errors.ts (44 lines) ← DUPLICATE
|
|
└── product-config.ts (7 lines) ← DUPLICATE
|
|
```
|
|
|
|
### After Refactor
|
|
|
|
```
|
|
services/platform-service/src/lib/
|
|
├── blob.ts (service-specific, stays)
|
|
└── (all others → @bytelyst/*)
|
|
|
|
services/billing-service/src/lib/
|
|
└── (empty — all moved to @bytelyst/*)
|
|
|
|
services/growth-service/src/lib/
|
|
└── (empty — all moved to @bytelyst/*)
|
|
|
|
services/tracker-service/src/lib/
|
|
└── (empty — all moved to @bytelyst/*)
|
|
|
|
Each server.ts: ~85 lines → ~15 lines
|
|
Each config.ts: ~30 lines → ~5 lines (just the extension schema)
|
|
```
|
|
|
|
---
|
|
|
|
## Appendix B: Ecosystem Metrics
|
|
|
|
| Metric | Before | After | Delta |
|
|
| --------------------------------------- | ---------------- | ------------------ | --------- |
|
|
| Duplicated infrastructure LOC | ~1,856 | ~275 | **-85%** |
|
|
| Number of `errors.ts` files | 4 | 1 | **-75%** |
|
|
| Number of `cosmos.ts` files | 6 | 1 | **-83%** |
|
|
| Number of `auth/jwt` files | 5 | 1 | **-80%** |
|
|
| Number of `server.ts` boilerplate lines | 351 | ~60 | **-83%** |
|
|
| Time to create new Fastify service | ~2 hours | ~20 min | **-83%** |
|
|
| Time to fix a JWT vulnerability | 5 PRs | 1 PR | **-80%** |
|
|
| Design token sync effort (MindLyst) | Manual (5 files) | Automated (1 JSON) | **-100%** |
|