All packages under packages/ are workspace-internal. None are published to npm individually. They are consumed by apps/web, apps/api, and each other via workspace:* references.
Path: packages/auth/
Owns all auth logic so neither app needs to know how Better Auth is configured.
Exports:
auth— Better Auth server instance (used byapps/webroute handler)resolveSession(headers)— validate a session cookie and return the session + user; used by every protected Hono routecheckPermission(session, action)— RBAC check against org roleROLES— role constants (owner,admin,member)sendAuthEmail(type, to, data)— mailer abstraction: tries Resend → SMTP → in-memory capture
Dependencies: @acme/config, @acme/db, @acme/jobs, better-auth, nodemailer, resend
Path: packages/config/
Validates env vars at startup using Zod. Both apps import apiEnv or webEnv — if required vars are missing the process exits immediately with a clear error.
Exports:
apiEnv— validated env object forapps/apiwebEnv— validated env object forapps/web- individual Zod schemas for each env section
Dependencies: zod
Path: packages/db/
All persistence logic lives here. Apps never import Drizzle directly.
Exports:
db— Drizzle client instance (postgres.js driver)- Schema tables:
users,sessions,organizations,members,invitations,auditLogs,webhookEndpoints,webhookDeliveries - Repository functions:
getUserById,getUsersByOrggetPendingInvitations,getInvitationByIdgetAuditLogs,insertAuditLoggetWebhookEndpoints,insertWebhookEndpoint,deleteWebhookEndpointinsertWebhookDelivery,updateWebhookDelivery
The auth schema (packages/db/src/schema/auth.ts) is generated by pnpm auth:generate and should not be edited manually.
Dependencies: @acme/config, @acme/shared, drizzle-orm, postgres
Path: packages/jobs/
BullMQ queue wiring and domain event helpers. Designed to degrade gracefully when Redis is absent.
Exports:
getQueue(name)— returns a BullMQ Queue instance (or no-op if Redis unavailable)getWorker(name, processor)— returns a BullMQ Worker (or no-op)domainEvents— event emitter for internal fan-out (org.member.added, etc.)- Job payload types:
InviteEmailPayload,WebhookDeliveryPayload - Feature flag helpers:
isAsyncInviteEmailEnabled(),isOutgoingWebhooksEnabled()
Dependencies: @acme/config, bullmq
Path: packages/logger/
Pino-based structured logger with optional Loki shipping.
Exports:
createLogger(context)— returns a Pino logger instance scoped to a request or service contextlokiTransport— Pino transport that ships to Loki (activated whenAPI_LOG_TO_LOKI=true)
Log levels follow Pino conventions. In development, logs are pretty-printed. In production, logs are JSON.
Dependencies: @acme/config, pino, pino-loki
Path: packages/observability/
OpenTelemetry SDK bootstrap. Call initOtel() once at process start in apps/api.
Exports:
initOtel()— initializes NodeSDK with OTLP HTTP exporter; no-op whenOTEL_EXPORTER_OTLP_ENDPOINTis unsetgetTracer(name)— returns an OTel tracerwithSpan(name, fn)— wraps an async function in a span
Dependencies: @acme/config, @opentelemetry/sdk-node, @opentelemetry/exporter-trace-otlp-http
Path: packages/shared/
Transport-neutral contracts used by both web and API. Nothing in this package imports from other @acme/* packages.
Exports:
- Zod schemas for API request/response shapes
- TypeScript types inferred from those schemas
ok(data)/err(code, message)— response envelope builders- Shared constants: pagination defaults, role labels, etc.
Dependencies: zod only
Path: packages/ui/
shadcn-based React component library. Components are thin wrappers over Base UI and Radix primitives styled with Tailwind.
Exports:
- Individual components via
@acme/ui/components/<name>path exports @acme/ui/globals.css— Tailwind base styles- Utility:
cn(...classes)— clsx + tailwind-merge
Peer dependencies: react, react-dom
Path: packages/cli/
TypeScript source for the create-acme-platform npm CLI. Not consumed by any other workspace package.
The build pipeline (tsup) bundles this to dist/index.mjs which is then copied to dist/create-acme-platform/bin/create-acme-platform.mjs and published to npm as part of the release.
Exports (internal, for tests):
toSlug(input)— converts a directory name to a valid npm package nameparseFlags(argv)— CLI argument parsercopyTemplate,removeObservability,patchPackageJson— scaffold operationsrunWizard()— interactive prompt flow via@clack/prompts
Path: packages/eslint-config/
Shared flat ESLint configurations. Each app and package extends one of:
base.js— TypeScript + general rulesnext.js— extends base + Next.js pluginreact.js— extends base + React plugin
Path: packages/typescript-config/
Shared tsconfig presets. Each app/package extends one of:
| Preset | Used by |
|---|---|
base.json |
Base for all other presets |
node.json |
All packages/* and apps/api |
react-library.json |
packages/ui |
nextjs.json |
apps/web |