5.9 KiB
5.9 KiB
System Patterns
This document outlines the system architecture, key technical decisions, and design patterns used in the G1 TypeScript Common Packages repository.
System Architecture
The repository follows a monorepo architecture using pnpm workspaces:
g1-ts-common-packages/
├── packages/ # All packages are stored here
│ ├── sse-client/ # Server-Sent Events client package
│ └── [future-packages]/ # Additional packages will be added here
├── scripts/ # Utility scripts for the repository
├── docs/ # Documentation
└── .github/workflows/ # CI/CD workflows
Each package within the monorepo is designed to be:
- Independent: Can be used without other packages from the monorepo
- Focused: Solves a specific problem domain
- Well-documented: Includes comprehensive documentation
- Well-tested: Includes unit tests
Key Technical Decisions
1. Monorepo Structure
- Decision: Use a monorepo structure with pnpm workspaces
- Rationale:
- Simplifies management of multiple related packages
- Enables sharing of configuration and tooling
- Facilitates coordinated changes across packages
- Reduces duplication of dependencies
2. Package Scope
- Decision: Use the
@g1
scope for all packages - Rationale:
- Provides namespace isolation
- Makes it clear that packages belong to Generation One
- Prevents name collisions with other packages
3. Package Versioning
- Decision: Start packages at version 0.2.0
- Rationale:
- Follows Generation One's standard starting version
- Allows for pre-1.0 breaking changes as packages mature
4. Publishing Strategy
- Decision: Publish packages to Gitea registry
- Rationale:
- Keeps packages within Generation One's infrastructure
- Provides access control for private packages (if needed in the future)
- Simplifies CI/CD integration
5. TypeScript First
- Decision: Use TypeScript for all packages
- Rationale:
- Provides type safety
- Improves developer experience
- Enables better tooling and IDE support
- Reduces runtime errors
Design Patterns
SSE Client Package
The SSE Client package demonstrates several design patterns:
1. Singleton Pattern
- Used in the
SSEConnectionManager
to ensure only one instance manages all connections - Provides a global point of access to the connection manager
export class SSEConnectionManager {
private static instance: SSEConnectionManager;
private constructor() {}
public static getInstance(): SSEConnectionManager {
if (!SSEConnectionManager.instance) {
SSEConnectionManager.instance = new SSEConnectionManager();
}
return SSEConnectionManager.instance;
}
// ...
}
2. Factory Pattern
- Helper functions like
getSSEConnection
act as factories to create or retrieve SSE client instances - Simplifies the creation and management of SSE connections
export function getSSEConnection(url: string, id: string, options: SSEClientOptions = {}): SSEClient {
return SSEConnectionManager.getInstance().getConnection(url, id, options);
}
3. Observer Pattern
- The SSE client implements an event-based system where clients can subscribe to events
- Uses the browser's
EventTarget
or a custom implementation in Node.js
public on(eventName: string, listener: (event: any) => void): SSEClient {
if (!this.eventListeners.has(eventName)) {
this.eventListeners.set(eventName, new Set());
}
this.eventListeners.get(eventName)!.add(listener);
// ...
return this;
}
4. Options Pattern
- Uses an options object with defaults for configuration
- Allows for flexible and extensible configuration
export interface SSEClientOptions {
headers?: Record<string, string>;
withCredentials?: boolean;
heartbeatTimeout?: number;
// ...
}
const DEFAULT_OPTIONS: Required<Omit<SSEClientOptions, 'headers'>> = {
withCredentials: true,
heartbeatTimeout: 21600000, // 6 hours
// ...
};
5. Exponential Backoff Pattern
- Implements exponential backoff for reconnection attempts
- Helps prevent overwhelming the server during outages
private async reconnect(): Promise<void> {
this.retryCount++;
// Calculate delay with exponential backoff
const delayMs = Math.min(
this.options.initialRetryDelay * Math.pow(2, this.retryCount - 1),
this.options.maxRetryDelay
);
// ...
}
Critical Implementation Paths
SSE Client Connection Flow
- Initialization: Create an SSE client with URL and options
- Connection: Call
connect()
to establish the connection - Event Handling: Register event listeners with
on(eventName, listener)
- Error Handling: Automatic reconnection with exponential backoff
- Cleanup: Close connection with
close()
or use automatic cleanup
SSE Client Connection Management
- Get/Create Connection: Use
getSSEConnection(url, id, options)
to get or create a connection - Connection Reuse: Connections are reused based on the provided ID
- Connection Cleanup: Close specific connections with
closeSSEConnection(id)
or all withcloseAllSSEConnections()
- Automatic Cleanup: Set up with
setupSSECleanup()
to close connections on page unload
Component Relationships
- SSEClient: Core class that handles the SSE connection and events
- SSEConnectionManager: Singleton that manages multiple SSE connections
- Helper Functions: Provide simplified access to the connection manager
getSSEConnection
: Gets or creates a connectioncloseSSEConnection
: Closes a specific connectioncloseAllSSEConnections
: Closes all connectionssetupSSECleanup
: Sets up automatic cleanup
- Debug Utilities: Provide debug logging capabilities
debugLog
,debugInfo
,debugWarn
,debugError