I. Shared Platform Opportunities
Principles for Shared Code
Before listing candidates, three principles must be stated clearly:
- Only extract when 2+ repos consume it with divergent release cycles. If only one app uses something, it stays in that app.
- Shared packages must have an owner. No ownerless packages — they become junk drawers.
- Primitives only at the shared layer. Types, config schemas, HTTP clients. Never business logic.
The greatest risk in any shared-code initiative is the "shared junk drawer" — a package that accumulates utilities because no one wants to own them, resulting in an untested, poorly documented catch-all.
Package Candidates
@soreto/types — Shared TypeScript Types
Priority: HIGH
Consumers: soreto-melissa, reverb-backend (after TS migration), soreto-zoe
What to include:
// Entity types (from reverb-backend API response shapes)
interface User { id: string; email: string; roles: Role[]; ... }
interface Role { id: string; name: RoleName; }
interface Campaign { id: string; clientId: string; name: string; ... }
interface CampaignVersion { id: string; campaignId: string; ... }
interface Order { id: string; campaignVersionId: string; status: OrderStatus; ... }
interface SharedUrl { id: string; userId: string; campaignVersionId: string; ... }
interface Reward { id: string; orderId: string; status: RewardStatus; ... }
// Enum types
type RoleName = 'admin' | 'user' | 'client' | 'clientUser' | 'financial' | 'guest' | 'system' | 'sales' | 'mpUser' | 'tech' | 'saas';
type OrderStatus = 'PENDING' | 'CONFIRMED' | 'CANCELLED' | ...;
type RewardStatus = 'ADDED' | 'DONE' | 'BLOCKED_REWARD_LIMIT' | 'EXPIRED';
// API response envelope
interface ApiResponse<T> { data: T; total?: number; page?: number; }
interface PaginatedResponse<T> extends ApiResponse<T[]> { totalPages: number; }
Source: Extract from soreto-melissa/types/ and soreto-melissa/shared/constants.ts, then validate against reverb-backend's actual API responses.
What to exclude: Database model types (stay in reverb-backend), Next.js-specific types (stay in melissa), Bull job types (stay in zoe).
@soreto/api-client — Typed reverb-backend HTTP Client
Priority: HIGH
Consumers: soreto-zoe (replaces local_modules/reverb), potentially soreto-melissa (replaces raw axios calls)
What to include:
// A typed, versioned replacement for local_modules/reverb/index.js
export interface ReverbClientOptions {
apiBaseUrl: string;
username: string;
password: string;
}
export class ReverbClient {
static create(options: ReverbClientOptions): Promise<ReverbClient>;
postReward: {
getCampaignVersionsEnabledV2(daysBack: number): Promise<CampaignVersion[]>;
getOrders(campaignVersionId: string, minutesBack: number, page: number, pageSize: number): Promise<Order[]>;
createOrderPostReward(payload: CreateOrderPostRewardPayload): Promise<OrderPostReward>;
// ...
};
notification: {
send(data: NotificationPayload): Promise<void>;
// ...
};
zendesk: {
leads: {
create(payload: ZendeskLeadPayload): Promise<ZendeskLead>;
// ...
};
};
}
Why this matters: The current local_modules/reverb client has no types. If reverb-backend changes an endpoint, soreto-zoe breaks silently at runtime. With @soreto/api-client, breaking changes are caught at compile time.
@soreto/constants — Shared Constants
Priority: MEDIUM
Consumers: soreto-melissa (already has some), reverb-backend (after TS migration), soreto-zoe
What to include:
export enum RoleName {
ADMIN = 'admin',
USER = 'user',
CLIENT = 'client',
CLIENT_USER = 'clientUser',
// ...
}
export const CAMPAIGN_TYPES = {
ON_SITE_REFERRAL: 'on_site_referral',
MARKETPLACE: 'marketplace',
} as const;
export const COUNTRIES: Country[] = [/* ... */];
export const ORDER_STATUSES = {
ADDED: 'ADDED',
DONE: 'DONE',
BLOCKED_REWARD_LIMIT: 'BLOCKED_REWARD_LIMIT',
EXPIRED: 'EXPIRED',
} as const;
Source: soreto-melissa/shared/constants.ts + constants from reverb-backend/config/constants.js + soreto-zoe processor constants.
@soreto/config — Environment Variable Loading
Priority: MEDIUM
Consumers: All four repos (as they are refactored to use standard env vars)
What to include:
// Validated environment variable loading with clear error messages
export function loadConfig(schema: ConfigSchema): Config;
// Standard config schemas
export const reverb_backend_schema: ConfigSchema;
export const zoe_schema: ConfigSchema;
Why: The current four approaches (inline defaults, @@PLACEHOLDER@@, config.js functions, .env files) are all different. Standardizing on .env with validated loading (e.g., using zod or envalid) ensures secrets fail loudly when missing rather than silently using defaults.
@soreto/auth-utils — Cookie and JWT Utilities
Priority: LOW-MEDIUM
Consumers: reverb-backend (replace soreto-cookie-jam), soreto-melissa (if cookie handling is needed)
What to include:
- Cookie packing/unpacking (from soreto-cookie-jam)
- JWT decode utilities
- Session token validation helpers
Note: Authentication logic (passport strategies, login flows) stays in reverb-backend. Only generic, stateless utilities belong here.
Tooling Packages
These are infrastructure packages that reduce configuration drift:
| Package | What it provides | Priority |
|---|---|---|
@soreto/eslint-config |
Shared ESLint baseline (extends eslint:recommended + TypeScript) |
HIGH |
@soreto/tsconfig |
Shared TypeScript base configs (tsconfig.base.json) |
HIGH |
@soreto/jest-config |
Shared Jest configuration with standard transforms | MEDIUM |
@soreto/prettier-config |
Shared Prettier rules | LOW |
@soreto/tsconfig example:
// packages/tsconfig/base.json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"skipLibCheck": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
}
}
Frontend Shared Packages
These are specific to the frontend apps:
@soreto/ui — Shared PrimeReact Component Wrappers
Priority: MEDIUM (becomes relevant once reverb-react modernization on Next.js 16 is complete)
Once both apps/legacy-portal/ (modernized reverb-react) and apps/platform-ui/ (melissa) are on Next.js 16 + React 19 + PrimeReact 10, a shared UI component wrapper package becomes viable. Both apps will share the same React version and component library, making extraction worthwhile. Don't extract prematurely — wait until the rewrite is complete and duplication is confirmed.
@soreto/hooks — Shared React Hooks
Priority: LOW (defer until a second frontend exists)
Same reasoning — premature extraction without a second consumer creates a junk drawer.
What Should NOT Become Shared
| Item | Reason |
|---|---|
| Database migration files | Each app owns its schema — migrations must stay colocated |
| Passport.js authentication strategies | Implementation detail of reverb-backend |
| Affiliate processor logic (Awin, Rakuten, etc.) | Business logic specific to soreto-zoe |
| Email templates | Context-specific, managed by the service that sends them |
| Express middleware | App-specific request/response concerns |
Business service implementations (campaign.js, reward.js) |
Domain logic, not infrastructure |
Next.js app/ route files |
Not shareable by framework design |
| Soreto-specific UI components | Wait for second consumer before extracting |
Avoiding the Junk Drawer
Rules to enforce:
- Every shared package must have a
CODEOWNERSentry mapping it to a specific team or person. - Every shared package must have a
CHANGELOG.mdupdated on each release. - No shared package may depend on another shared package's business logic. Dependencies between packages:
@soreto/api-clientmay depend on@soreto/types.@soreto/typesmay not depend on@soreto/api-client. - If a shared package is only imported by one app, move it back. Run
pnpm why @soreto/constantsquarterly. - Shared packages must have their own tests. No test coverage = no shipping.
Proposed Package Dependency Graph
@soreto/types (no soreto deps)
│
▼
@soreto/constants (depends on @soreto/types for type annotations)
│
▼
@soreto/api-client (depends on @soreto/types for response types)
│
▼
apps/zoe (uses api-client, types, constants)
apps/platform-ui (uses types, constants, ui-components)
apps/platform-api (uses types, constants — after TS migration)
@soreto/eslint-config (no soreto deps — pure tooling)
@soreto/tsconfig (no soreto deps — pure tooling)
@soreto/jest-config (no soreto deps — pure tooling)