Skip to content

@doable/shared

Cross-cutting types, constants, AI provider catalog, the KV-store abstraction, and a small security helper. Lives at packages/shared/. Both services/api and apps/web import from it; if a value or type is used in both, it belongs here.

What's inside

File / dir Purpose
index.ts Re-exports everything below
constants.ts Plan + role metadata, PLAN_LIMITS, ordering helpers (compareRoles, comparePlans)
kv-store.ts KVStore interface + in-memory and Redis implementations, picked by REDIS_URL
types/index.ts Core entity types (User, Workspace, WorkspaceMember, Project, AiSession, Credits, FeatureFlag, GitHubCopilotAccount, AiProvider, WorkspaceAiSettings, …) plus the WORKSPACE_PLANS / WORKSPACE_ROLES source-of-truth arrays
types/api.ts API request and response shapes
types/ai.ts ConversationMessage, StreamEvent, ToolCall, EngineOptions, Plan, ClarificationQuestion
types/mcp.ts MCP connector and tool types shared by api + web
types/ws.ts WebSocket message envelope types
ai/index.ts Re-exports the provider catalog and types
ai/provider-types.ts ProviderPreset, AuthMethod, model and tag enums
ai/provider-catalog.ts Single source of truth for the 60+ supported AI providers; surfaces PROVIDER_CATALOG, PROVIDER_COUNT
ai/provider-data-cloud-major.ts OpenAI, Anthropic, Google, Azure, Bedrock, Vertex preset data
ai/provider-data-cloud-regional.ts Moonshot, DashScope, Zhipu, MiniMax, Cerebras, Hyperbolic, …
ai/provider-data-cloud-special.ts Aggregators (OpenRouter, Together, Fireworks) and infra providers
ai/provider-data-local.ts Ollama, LM Studio, vLLM, llama.cpp, Jan, LocalAI, GPT4All, …
security/env-perms-check.ts checkEnvFilePerms(): warns when .env is group or world readable in production

KV store

import { getKVStore } from "@doable/shared";

const kv = getKVStore();
await kv.set("rate:user-42", 1, 60_000); // ttl in ms
const v = await kv.get<number>("rate:user-42");
const next = await kv.incr("counter:logins");

The interface (kv-store.ts) is get, set, delete, incr, close. Both implementations honour TTL.

  • MemoryStore: a Map<string, MemEntry> with setInterval sweep every 30 seconds. The interval is .unref()'d so it does not keep the process alive.
  • RedisStore: lazy-imports ioredis only when REDIS_URL is set, JSON-encodes values, uses SET key value PX ttl for expiry and INCR + PEXPIRE key ttl for atomic counters.

The factory getKVStore() is the singleton entry point. The CLAUDE.md rule "No Redis required, in-memory KV works out of the box" relies on this: rate limiting, presence cache, OAuth state, and per-request idempotency keys all consume the same interface so swapping in Redis is one env var.

resetKVStore() exists for tests and clears the singleton.

Plans, roles, and limits

types/index.ts holds the source-of-truth arrays:

export const WORKSPACE_PLANS = ["free", "pro", "business", "enterprise"] as const;
export const WORKSPACE_ROLES = ["viewer", "member", "admin", "owner"] as const;
export const PLATFORM_ADMIN_ROLES = ["admin", "owner"] as const;

Adding a new plan or role means:

  1. Append to the array.
  2. Add a row to PLAN_META or ROLE_META in constants.ts.
  3. Run a Postgres migration (ALTER TYPE workspace_plan ADD VALUE '<new>').

All Zod schemas, labels, dropdowns, role hierarchies, and plan-limit lookups derive from the array automatically. compareRoles(a, b) and comparePlans(a, b) return signed indices; use them instead of raw === comparisons.

PLAN_LIMITS (constants.ts) holds the shipping defaults that the platform admin can override per plan in /admin/plan-limits: maxProjects, maxMembers, dailyCredits, monthlyCredits, maxFileSize, customDomains, analytics, prioritySupport.

AI provider catalog

ai/provider-catalog.ts is the single source of truth for the 60+ supported AI providers shown in the setup wizard, in /ai-settings, and in the Docker setup.sh env-var sniffer. Each ProviderPreset carries:

  • id, name, description, tags (popular, regional, local, …)
  • category (cloud-major, cloud-regional, cloud-special, local)
  • authMethod (apiKey, oauth, none for local providers)
  • defaultBaseUrl, baseUrlEditable, baseUrlTemplate
  • defaultModels and apiKeyHelpUrl
  • Optional warnings and freeTier flag

PROVIDER_COUNT is a constant; the setup wizard renders it ("Show all N providers") so it stays in sync without manual count fixes.

Security helper

security/env-perms-check.ts exports checkEnvFilePerms(envPath?). When NODE_ENV=production and the .env file's mode lets group or other read (mask 0o044), it logs a [SECURITY] warning to stderr. It also warns when the file's owner UID differs from the process UID (perms drift). The check is non-fatal and is safe to call at API startup.

When to add to shared

Add a type or constant here when two or more packages in the monorepo need it. If only the API uses it, keep it in services/api. If only the frontend uses it, keep it in apps/web/src/lib. Do not import from services/api into apps/web directly; route the shared piece through this package.

See also