Skip to content

Shared Packages

Monorepo packages for code sharing across applications.

Current Packages

packages/
├── auth-client/        # Auth & org client (Better Auth + TanStack Query hooks)
├── ui/                 # Shared UI components (shadcn/ui)
├── eslint-config/      # ESLint configurations
├── logger/             # Structured logging (adaptor pattern, zero dependencies)
├── typescript-config/  # TypeScript configurations
├── shared-types/       # Shared TypeScript types
└── vitest-config/      # Vitest test configurations

Package Details

@jubiloop/auth-client

Authentication and organization client for frontend apps. Wraps Better Auth with TanStack Query hooks for session management, sign-in/up/out flows, and organization operations.

General API calls (events, health checks, etc.) use Tuyau — an end-to-end type-safe client generated from the AdonisJS backend. auth-client handles only auth and org concerns.

Features

  • Better Auth Client Factory (createBetterAuthClient): Configured with organization plugin
  • Auth Hooks: useAuth (sign-in, sign-up, sign-out, session state)
  • Organization Hooks: list, create, switch, update, delete organizations; manage members
  • Session Queries: Cache-first session fetching for route guards (getSessionData, isAuthenticated)
  • Centralized Query Keys: defaultQueryKeys for consistent cache management across apps

Package Structure

packages/auth-client/
├── src/
│   ├── auth/              # Better Auth integration
│   │   ├── client.ts      # Auth client factory
│   │   ├── types.ts       # Auth type definitions
│   │   └── index.ts
│   ├── hooks/             # TanStack Query hooks
│   │   ├── auth.ts        # Sign-in, sign-up, sign-out, session
│   │   ├── organization.ts # Org CRUD, switching, member management
│   │   └── index.ts
│   ├── queries/           # Query option factories
│   │   ├── session.ts     # Session query options
│   │   ├── organization.ts # Organization query options
│   │   └── index.ts
│   ├── keys/              # Query key management
│   │   ├── defaults.ts    # Static defaultQueryKeys object
│   │   └── index.ts
│   └── index.ts           # Main package exports
├── tsup.config.ts         # Build configuration
└── package.json

Build System

Uses tsup for bundling:

  • Multiple entry points for optimal tree-shaking
  • ESM and CommonJS outputs
  • TypeScript declarations

@jubiloop/ui

Centralized UI component library using shadcn/ui. See UI Components for full documentation.

@jubiloop/eslint-config

Shared ESLint configurations:

  • base.js - Base JavaScript/TypeScript rules
  • next.js - Next.js-specific rules
  • react-internal.js - React rules
  • tanstack.js - TanStack-specific rules

@jubiloop/typescript-config

Shared TypeScript configurations:

  • base.json - Base configuration
  • nextjs.json - Next.js applications
  • react-library.json - React libraries

@jubiloop/shared-types

Centralized TypeScript type definitions shared between frontend and backend applications, eliminating duplication and ensuring type consistency across the stack.

Purpose

The shared-types package solves the problem of duplicate type definitions between backend and frontend by providing a single source of truth for all shared TypeScript interfaces and types.

Features

  • Entity Interfaces: Core domain models (e.g., IUser, IOrganization)
  • API Response Types: Standardized API response structures (IApiResponse, IApiMessage, IApiError)
  • DTOs: Legacy DTO interfaces for API communication (IUserMinimalDTO) — being replaced by auto-generated transformer types
  • Request Types: API request interfaces (ISubscribeNewsletterRequest)
  • Health Check Types: System health monitoring interfaces (IHealthResponse, IDetailedHealthResponse)

Architecture

The package uses wildcard exports instead of barrel exports to:

  • Avoid circular dependency issues
  • Enable better tree-shaking
  • Provide cleaner import paths

Build System

The package employs a two-step build process:

Production Build (pnpm run build):

  • Uses tsup only
  • Generates JavaScript and type definitions
  • No declaration maps for security (prevents source exposure)

Development Build (pnpm run dev):

  • Uses tsup + tsc concurrently
  • Generates declaration maps (.d.ts.map files)
  • Enables IDE "Go to Definition" navigation to source TypeScript files

Package Structure

packages/shared-types/
├── src/
│   ├── entities/        # Domain entity interfaces
│   │   └── user.ts      # IUser interface
│   ├── dtos/            # Data Transfer Objects
│   │   └── user.ts      # User DTOs
│   ├── api/
│   │   ├── requests/    # API request types
│   │   │   └── newsletter.ts
│   │   └── responses/   # API response types
│   │       ├── common.ts     # IApiResponse, IApiError
│   │       └── health.ts     # Health check types
│   └── index.ts         # Re-exports (minimal)
├── tsup.config.ts       # Build configuration
└── package.json         # Package configuration

Usage Examples

typescript
// Import from specific paths (recommended)
import type { IApiResponse } from '@jubiloop/shared-types/api/responses/common'
import type { IUserMinimalDTO } from '@jubiloop/shared-types/dtos/user'
import type { IUser } from '@jubiloop/shared-types/entities/user'

// Backend: Type-safe API responses
const response: IApiResponse<IUser> = {
  success: true,
  data: user,
  message: { type: 'success', text: 'User fetched' },
}

// Frontend: Type-safe API consumption
const { data }: IApiResponse<IUser> = await api.get('/users/me')

TypeScript Naming Conventions

The package follows these conventions for clarity:

  • I prefix for interfaces: IUser, IApiClient, IHealthResponse
  • T prefix for type aliases: TSession, TAuthResponse (if needed)

This helps distinguish between interfaces and types at a glance.

@jubiloop/logger

Zero-dependency structured logging package with adaptor pattern.

Architecture

  • Logger class — framework-agnostic, delegates output to a TLogAdaptor
  • ConsoleAdaptor — dev output with structured context
  • NoopAdaptor — production/test (suppresses all output)
  • Apps implement their own adaptors (server wraps AdonisJS Pino)

Usage

typescript
import { createLogger } from '@jubiloop/logger'

const logger = createLogger({ appEnv: 'local' })
logger.error('Event creation failed', { key: 'events:create', source: 'http', error })

Log Context

Every log call accepts a TLogContext with:

  • key — filterable composite key: 'domain:action' (e.g., 'events:create', 'auth:signIn')
  • source — execution context: 'http', 'worker', 'cron', 'webhook'
  • organizationId, userId — optional identifiers for filtering
  • error — the error object (if any)

Consumer Setup

AppLogger fileAPP_ENV source
Webappsrc/utils/logger.tsimport.meta.env.VITE_APP_ENV
Marketingsrc/utils/logger.tsprocess.env.NEXT_PUBLIC_APP_ENV
Serverapp/lib/app_logger.tsenv.get('APP_ENV') via PinoAdaptor

@jubiloop/vitest-config

Test configuration for Vitest (currently unused as tests are not yet implemented).

Using Packages

Packages are linked using pnpm workspaces:

json
{
  "dependencies": {
    "@jubiloop/ui": "*",
    "@jubiloop/eslint-config": "*"
  }
}

Best Practices

  1. Keep packages focused on truly shared code
  2. Avoid app-specific logic in packages
  3. Use TypeScript for all package code
  4. Document public APIs with JSDoc comments

Built with ❤️ by the Jubiloop team