@hkdigital/lib-core 0.5.91 → 0.5.93
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -9
- package/dist/browser/info/device.js +9 -7
- package/dist/logging/README.md +15 -53
- package/dist/services/PATTERNS.md +476 -0
- package/dist/services/PLUGINS.md +520 -0
- package/dist/services/README.md +156 -229
- package/dist/ui/components/game-box/ScaledContainer.svelte +3 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,28 @@
|
|
|
3
3
|
Core library that we use to power up our SvelteKit projects
|
|
4
4
|
|
|
5
5
|
This is a library for [SvelteKit](https://svelte.dev/) projects.
|
|
6
|
-
It contains common code, base components and documentation that help
|
|
6
|
+
It contains common code, base components and documentation that help
|
|
7
|
+
you with setting up a new project.
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Using the library](#using-the-library)
|
|
12
|
+
- [Install](#install)
|
|
13
|
+
- [Peer Dependencies](#peer-dependencies)
|
|
14
|
+
- [Design System & Configuration](#design-system--configuration)
|
|
15
|
+
- [Logging System](#logging-system)
|
|
16
|
+
- [Documentation](#documentation)
|
|
17
|
+
- [Update](#update)
|
|
18
|
+
- [Available scripts](#available-scripts)
|
|
19
|
+
- [Import Validation](#import-validation)
|
|
20
|
+
- [Import Patterns and Export Structure](#import-patterns-and-export-structure)
|
|
21
|
+
- [CSS Architecture](#css-architecture-appcss)
|
|
22
|
+
- [Critical: data-theme Attribute](#critical-data-theme-attribute)
|
|
23
|
+
- [Building the library](#building-the-library)
|
|
24
|
+
- [Running the showcase app](#running-the-showcase-app)
|
|
25
|
+
- [Developing](#developing)
|
|
26
|
+
- [Publishing](#publishing)
|
|
27
|
+
- [Contribute](#contribute)
|
|
7
28
|
|
|
8
29
|
## Using the library
|
|
9
30
|
|
|
@@ -247,14 +268,47 @@ The library includes a comprehensive logging system that provides:
|
|
|
247
268
|
|
|
248
269
|
## Documentation
|
|
249
270
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
- **
|
|
257
|
-
|
|
271
|
+
Comprehensive documentation organized by topic.
|
|
272
|
+
|
|
273
|
+
### Getting Started
|
|
274
|
+
|
|
275
|
+
Start here if you're setting up a new project or library:
|
|
276
|
+
|
|
277
|
+
- **[New project setup](./docs/setup/new-project.md)** - Complete guide
|
|
278
|
+
for setting up a SvelteKit application with lib-core
|
|
279
|
+
- **[New library setup](./docs/setup/new-lib.md)** - Complete guide for
|
|
280
|
+
setting up a SvelteKit library with lib-core
|
|
281
|
+
|
|
282
|
+
### Architecture Guides
|
|
283
|
+
|
|
284
|
+
Learn how the different systems work together:
|
|
285
|
+
|
|
286
|
+
- **[Services & logging architecture](./docs/setup/services-logging.md)**
|
|
287
|
+
- How service management and logging integrate with SvelteKit
|
|
288
|
+
- **[Service patterns](./src/lib/services/PATTERNS.md)** - Best
|
|
289
|
+
practices and design patterns for implementing services
|
|
290
|
+
- **[Service plugins](./src/lib/services/PLUGINS.md)** - ConfigPlugin
|
|
291
|
+
and custom plugin development
|
|
292
|
+
|
|
293
|
+
### API Reference
|
|
294
|
+
|
|
295
|
+
Detailed API documentation for each module:
|
|
296
|
+
|
|
297
|
+
- **[Logging](./src/lib/logging/README.md)** - Server and client
|
|
298
|
+
logging API, log levels, and formatters
|
|
299
|
+
- **[Services](./src/lib/services/README.md)** - ServiceBase and
|
|
300
|
+
ServiceManager API reference
|
|
301
|
+
- **[Design system](./src/lib/design/README.md)** - Design tokens,
|
|
302
|
+
theming, and UI utilities
|
|
303
|
+
- **[Vite configuration](./src/lib/config/README.md)** - Build
|
|
304
|
+
configuration and optimization
|
|
305
|
+
|
|
306
|
+
### Configuration
|
|
307
|
+
|
|
308
|
+
Reference documentation for configuration files:
|
|
309
|
+
|
|
310
|
+
- **[Root config files](./docs/config/root-config-files.md)** - Guide
|
|
311
|
+
to vite.config.js, tailwind.config.js, and other root configs
|
|
258
312
|
|
|
259
313
|
### Update
|
|
260
314
|
|
|
@@ -131,13 +131,8 @@ export function getIsPhone() {
|
|
|
131
131
|
* @returns {boolean} true if mobile device
|
|
132
132
|
*/
|
|
133
133
|
export function getIsMobile() {
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
// Modern API - most reliable
|
|
137
|
-
// @ts-ignore
|
|
138
|
-
return navigator.userAgentData.mobile;
|
|
139
|
-
}
|
|
140
|
-
|
|
134
|
+
// Check Apple devices first - userAgentData.mobile is unreliable for
|
|
135
|
+
// iPads with M-series chips (reports false despite being mobile)
|
|
141
136
|
if (getIsAppleMobile()) {
|
|
142
137
|
return true;
|
|
143
138
|
}
|
|
@@ -146,6 +141,13 @@ export function getIsMobile() {
|
|
|
146
141
|
return true;
|
|
147
142
|
}
|
|
148
143
|
|
|
144
|
+
// @ts-ignore
|
|
145
|
+
if (navigator?.userAgentData?.mobile !== undefined) {
|
|
146
|
+
// Modern API - use as fallback
|
|
147
|
+
// @ts-ignore
|
|
148
|
+
return navigator.userAgentData.mobile;
|
|
149
|
+
}
|
|
150
|
+
|
|
149
151
|
return false;
|
|
150
152
|
}
|
|
151
153
|
|
package/dist/logging/README.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
Universal logging utilities for SvelteKit applications with
|
|
4
4
|
server/client/universal logger factories.
|
|
5
5
|
|
|
6
|
+
**See also:**
|
|
7
|
+
- **Architecture**: [docs/setup/services-logging.md](../../docs/setup/services-logging.md)
|
|
8
|
+
- How logging and services work together
|
|
9
|
+
- **Services**: [src/lib/services/README.md](../services/README.md) -
|
|
10
|
+
Service management system
|
|
11
|
+
- **Main README**: [README.md](../../README.md) - Library overview and
|
|
12
|
+
setup
|
|
13
|
+
|
|
6
14
|
## Installation
|
|
7
15
|
|
|
8
16
|
```bash
|
|
@@ -182,61 +190,15 @@ export function handleError({ error, event }) {
|
|
|
182
190
|
}
|
|
183
191
|
```
|
|
184
192
|
|
|
185
|
-
###
|
|
186
|
-
|
|
187
|
-
When integrating with a service management system, you can set up global
|
|
188
|
-
error handling and forward service logs to the main logger:
|
|
189
|
-
|
|
190
|
-
```javascript
|
|
191
|
-
import { ServiceManager } from '@hkdigital/lib-core/services/index.js';
|
|
192
|
-
import { initClientLogger } from '$lib/logging/client.js';
|
|
193
|
-
|
|
194
|
-
/** @type {ServiceManager} */
|
|
195
|
-
let manager;
|
|
196
|
-
|
|
197
|
-
export async function initClientServices() {
|
|
198
|
-
if (!manager) {
|
|
199
|
-
const logger = initClientLogger();
|
|
200
|
-
|
|
201
|
-
// Catch errors and unhandled promise rejections
|
|
202
|
-
|
|
203
|
-
// Log unhandled errors
|
|
204
|
-
window.addEventListener('error', (event) => {
|
|
205
|
-
logger.error(event, { url: window.location.pathname });
|
|
206
|
-
event.preventDefault();
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
// Log unhandled promise rejections
|
|
210
|
-
window.addEventListener('unhandledrejection', (event) => {
|
|
211
|
-
logger.error(event, { url: window.location.pathname });
|
|
212
|
-
// Ignored by Firefox
|
|
213
|
-
event.preventDefault();
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
manager = new ServiceManager({ debug: true });
|
|
217
|
-
|
|
218
|
-
// Listen to all log events and forward them to the logger
|
|
219
|
-
manager.onLogEvent((logEvent) => {
|
|
220
|
-
logger.logFromEvent(logEvent);
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
// Register services
|
|
224
|
-
manager.register(SERVICE_AUDIO, AudioService);
|
|
225
|
-
manager.register(SERVICE_EVENT_LOG, EventLogService);
|
|
226
|
-
manager.register(SERVICE_PLAYER_DATA, PlayerDataService);
|
|
227
|
-
}
|
|
193
|
+
### Service Integration
|
|
228
194
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
}
|
|
195
|
+
For integrating logging with the service management system, including
|
|
196
|
+
how to forward service logs to the main logger, see:
|
|
232
197
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
return manager;
|
|
238
|
-
}
|
|
239
|
-
```
|
|
198
|
+
- [Services README](../services/README.md) - ServiceManager log event
|
|
199
|
+
forwarding
|
|
200
|
+
- [Services & Logging Architecture](../../docs/setup/services-logging.md)
|
|
201
|
+
- Complete integration examples
|
|
240
202
|
|
|
241
203
|
## Development
|
|
242
204
|
|
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
# Service Patterns and Best Practices
|
|
2
|
+
|
|
3
|
+
Design patterns and best practices for implementing services with the
|
|
4
|
+
ServiceBase and ServiceManager system.
|
|
5
|
+
|
|
6
|
+
**See also:**
|
|
7
|
+
- **API Reference**: [README.md](./README.md) - ServiceBase and
|
|
8
|
+
ServiceManager API
|
|
9
|
+
- **Plugins**: [PLUGINS.md](./PLUGINS.md) - Plugin system and
|
|
10
|
+
ConfigPlugin
|
|
11
|
+
- **Architecture**: [docs/setup/services-logging.md](../../docs/setup/services-logging.md)
|
|
12
|
+
- Integration patterns
|
|
13
|
+
|
|
14
|
+
## Service Access Patterns
|
|
15
|
+
|
|
16
|
+
Services receive helpful utilities in their constructor options for
|
|
17
|
+
accessing other services and the manager.
|
|
18
|
+
|
|
19
|
+
### Basic Pattern
|
|
20
|
+
|
|
21
|
+
```javascript
|
|
22
|
+
/**
|
|
23
|
+
* Example service that depends on other services
|
|
24
|
+
*/
|
|
25
|
+
class AuthService extends ServiceBase {
|
|
26
|
+
/** @type {(<T>(serviceName: string) => T)} */
|
|
27
|
+
#getService;
|
|
28
|
+
|
|
29
|
+
/** @type {() => import('@hkdigital/lib-core/services/index.js').ServiceManager} */
|
|
30
|
+
#getManager;
|
|
31
|
+
|
|
32
|
+
constructor(serviceName, options) {
|
|
33
|
+
super(serviceName, options);
|
|
34
|
+
|
|
35
|
+
// Store service access utilities as private methods
|
|
36
|
+
this.#getService = options.getService; // Bound getService function
|
|
37
|
+
this.#getManager = options.getManager; // Function to get manager (lazy)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async authenticateUser(credentials) {
|
|
41
|
+
// Access other services with full type safety and error checking
|
|
42
|
+
const database = this.#getService('database');
|
|
43
|
+
const user = await database.findUser(credentials.username);
|
|
44
|
+
|
|
45
|
+
// Access manager for advanced operations when needed
|
|
46
|
+
const manager = this.#getManager();
|
|
47
|
+
const health = await manager.checkHealth();
|
|
48
|
+
|
|
49
|
+
return user;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Recommended Pattern: Private Methods
|
|
55
|
+
|
|
56
|
+
The recommended approach is to store service access functions as
|
|
57
|
+
**private methods** using the hash prefix. This pattern provides:
|
|
58
|
+
|
|
59
|
+
**Benefits:**
|
|
60
|
+
- **Keeps the API clean** - No public getService/getManager methods
|
|
61
|
+
exposed
|
|
62
|
+
- **Prevents serialization issues** - Private fields don't serialize
|
|
63
|
+
to JSON
|
|
64
|
+
- **Enforces proper encapsulation** - Service dependencies stay
|
|
65
|
+
internal
|
|
66
|
+
- **Provides type safety** - Full generic support with
|
|
67
|
+
`this.#getService<DatabaseService>('database')`
|
|
68
|
+
|
|
69
|
+
**Example:**
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
/**
|
|
73
|
+
* Unified service for tracking complete player data including progress
|
|
74
|
+
* and profile matches
|
|
75
|
+
*/
|
|
76
|
+
export default class PlayerService extends ServiceBase {
|
|
77
|
+
|
|
78
|
+
/** @type {(<T>(serviceName: string) => T)} */
|
|
79
|
+
#getService;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {string} serviceName
|
|
83
|
+
* @param {import('@hkdigital/lib-core/services/typedef.js').ServiceOptions} [options]
|
|
84
|
+
*/
|
|
85
|
+
constructor(serviceName, options) {
|
|
86
|
+
super(serviceName, options);
|
|
87
|
+
|
|
88
|
+
this.#getService = options?.getService;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async getPlayerProfile(playerId) {
|
|
92
|
+
// Access dependent services cleanly
|
|
93
|
+
const database = this.#getService('database');
|
|
94
|
+
const analytics = this.#getService('analytics');
|
|
95
|
+
|
|
96
|
+
const profile = await database.getPlayer(playerId);
|
|
97
|
+
const stats = await analytics.getPlayerStats(playerId);
|
|
98
|
+
|
|
99
|
+
return { ...profile, stats };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Service Access Methods
|
|
105
|
+
|
|
106
|
+
ServiceManager provides two access patterns:
|
|
107
|
+
|
|
108
|
+
```javascript
|
|
109
|
+
// 1. Permissive - returns undefined if not found/created
|
|
110
|
+
const service = manager.get('optional-service');
|
|
111
|
+
if (service) {
|
|
112
|
+
// Use service safely
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 2. Strict - throws error if not found/created
|
|
116
|
+
const service = manager.getService('required-service'); // Throws if missing
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**When to use each:**
|
|
120
|
+
- Use `get()` for optional services or when checking availability
|
|
121
|
+
- Use `getService()` for required dependencies (clearer error messages)
|
|
122
|
+
|
|
123
|
+
### Constructor Utilities Benefits
|
|
124
|
+
|
|
125
|
+
**Lightweight:**
|
|
126
|
+
Functions don't serialize, keeping services serialization-safe.
|
|
127
|
+
|
|
128
|
+
**Lazy access:**
|
|
129
|
+
Manager is only accessed when needed, avoiding circular dependencies
|
|
130
|
+
during initialization.
|
|
131
|
+
|
|
132
|
+
**Type safety:**
|
|
133
|
+
Full generic support with JSDoc annotations enables IDE autocomplete
|
|
134
|
+
and type checking.
|
|
135
|
+
|
|
136
|
+
**Error handling:**
|
|
137
|
+
Clear, consistent errors when services are missing or not yet
|
|
138
|
+
initialized.
|
|
139
|
+
|
|
140
|
+
## Configuration Patterns
|
|
141
|
+
|
|
142
|
+
### Initial Configuration vs Reconfiguration
|
|
143
|
+
|
|
144
|
+
Services should handle both initial setup and runtime reconfiguration
|
|
145
|
+
intelligently.
|
|
146
|
+
|
|
147
|
+
**Pattern:**
|
|
148
|
+
|
|
149
|
+
```javascript
|
|
150
|
+
class DatabaseService extends ServiceBase {
|
|
151
|
+
// eslint-disable-next-line no-unused-vars
|
|
152
|
+
async _configure(newConfig, oldConfig = null) {
|
|
153
|
+
if (!oldConfig) {
|
|
154
|
+
// Initial configuration - store all settings
|
|
155
|
+
this.connectionString = newConfig.connectionString;
|
|
156
|
+
this.maxConnections = newConfig.maxConnections || 10;
|
|
157
|
+
this.timeout = newConfig.timeout || 5000;
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Reconfiguration - handle changes intelligently
|
|
162
|
+
if (oldConfig.connectionString !== newConfig.connectionString) {
|
|
163
|
+
// Connection changed - need to reconnect
|
|
164
|
+
await this.connection?.close();
|
|
165
|
+
this.connectionString = newConfig.connectionString;
|
|
166
|
+
|
|
167
|
+
if (this.state === 'running') {
|
|
168
|
+
this.connection = await createConnection(this.connectionString);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (oldConfig.maxConnections !== newConfig.maxConnections) {
|
|
173
|
+
// Pool size changed - update without reconnect
|
|
174
|
+
this.maxConnections = newConfig.maxConnections;
|
|
175
|
+
await this.connection?.setMaxConnections(this.maxConnections);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (oldConfig.timeout !== newConfig.timeout) {
|
|
179
|
+
// Timeout changed - just update the setting
|
|
180
|
+
this.timeout = newConfig.timeout;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Key principles:**
|
|
187
|
+
- Check `!oldConfig` to detect initial configuration
|
|
188
|
+
- Compare old and new config to identify what changed
|
|
189
|
+
- Apply minimal changes (don't restart if not needed)
|
|
190
|
+
- Update running state when possible
|
|
191
|
+
|
|
192
|
+
### Configuration Defaults
|
|
193
|
+
|
|
194
|
+
Handle missing configuration gracefully with sensible defaults.
|
|
195
|
+
|
|
196
|
+
**Pattern:**
|
|
197
|
+
|
|
198
|
+
```javascript
|
|
199
|
+
async _configure(newConfig, oldConfig = null) {
|
|
200
|
+
// Use defaults for missing values
|
|
201
|
+
this.host = newConfig.host || 'localhost';
|
|
202
|
+
this.port = newConfig.port || 5432;
|
|
203
|
+
this.maxConnections = newConfig.maxConnections || 10;
|
|
204
|
+
this.timeout = newConfig.timeout || 5000;
|
|
205
|
+
this.retryAttempts = newConfig.retryAttempts ?? 3; // Use ?? for zero values
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Lifecycle Patterns
|
|
210
|
+
|
|
211
|
+
### Proper Resource Management
|
|
212
|
+
|
|
213
|
+
Always clean up resources in the stop method.
|
|
214
|
+
|
|
215
|
+
**Pattern:**
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
class WebSocketService extends ServiceBase {
|
|
219
|
+
async _start() {
|
|
220
|
+
this.ws = new WebSocket(this.url);
|
|
221
|
+
this.intervalId = setInterval(() => this.#ping(), 30000);
|
|
222
|
+
|
|
223
|
+
await new Promise((resolve) => {
|
|
224
|
+
this.ws.onopen = resolve;
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async _stop() {
|
|
229
|
+
// Clear timers
|
|
230
|
+
if (this.intervalId) {
|
|
231
|
+
clearInterval(this.intervalId);
|
|
232
|
+
this.intervalId = null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Close connections
|
|
236
|
+
if (this.ws) {
|
|
237
|
+
this.ws.close();
|
|
238
|
+
this.ws = null;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async _destroy() {
|
|
243
|
+
// Clean up any remaining resources
|
|
244
|
+
await this._stop();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Async Initialization
|
|
250
|
+
|
|
251
|
+
Keep `_configure()` lightweight, do heavy work in `_start()`.
|
|
252
|
+
|
|
253
|
+
**Good:**
|
|
254
|
+
|
|
255
|
+
```javascript
|
|
256
|
+
async _configure(newConfig, oldConfig = null) {
|
|
257
|
+
// Just store config
|
|
258
|
+
this.apiKey = newConfig.apiKey;
|
|
259
|
+
this.endpoint = newConfig.endpoint;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async _start() {
|
|
263
|
+
// Heavy work here
|
|
264
|
+
this.client = await createApiClient(this.apiKey, this.endpoint);
|
|
265
|
+
await this.client.authenticate();
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Bad:**
|
|
270
|
+
|
|
271
|
+
```javascript
|
|
272
|
+
async _configure(newConfig, oldConfig = null) {
|
|
273
|
+
// Don't do heavy work in configure
|
|
274
|
+
this.client = await createApiClient(newConfig.apiKey);
|
|
275
|
+
await this.client.authenticate(); // ❌ Heavy operation
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Health Check Patterns
|
|
280
|
+
|
|
281
|
+
### Return Useful Metrics
|
|
282
|
+
|
|
283
|
+
Health checks should return actionable information.
|
|
284
|
+
|
|
285
|
+
**Pattern:**
|
|
286
|
+
|
|
287
|
+
```javascript
|
|
288
|
+
async _healthCheck() {
|
|
289
|
+
const start = Date.now();
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
await this.connection.ping();
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
latency: Date.now() - start,
|
|
296
|
+
connections: this.connection.activeConnections,
|
|
297
|
+
queueSize: this.connection.queueSize
|
|
298
|
+
};
|
|
299
|
+
} catch (error) {
|
|
300
|
+
return {
|
|
301
|
+
error: error.message,
|
|
302
|
+
lastSuccessful: this.lastSuccessfulPing
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Implement Recovery Logic
|
|
309
|
+
|
|
310
|
+
Provide custom recovery for services that can auto-heal.
|
|
311
|
+
|
|
312
|
+
**Pattern:**
|
|
313
|
+
|
|
314
|
+
```javascript
|
|
315
|
+
async _recover() {
|
|
316
|
+
// Close broken connection
|
|
317
|
+
await this.connection?.close();
|
|
318
|
+
|
|
319
|
+
// Wait before reconnecting
|
|
320
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
321
|
+
|
|
322
|
+
// Recreate connection
|
|
323
|
+
this.connection = await createConnection(this.connectionString);
|
|
324
|
+
|
|
325
|
+
// Verify it works
|
|
326
|
+
await this.connection.ping();
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Error Handling Patterns
|
|
331
|
+
|
|
332
|
+
### Graceful Degradation
|
|
333
|
+
|
|
334
|
+
Handle errors without crashing the entire system.
|
|
335
|
+
|
|
336
|
+
**Pattern:**
|
|
337
|
+
|
|
338
|
+
```javascript
|
|
339
|
+
class CacheService extends ServiceBase {
|
|
340
|
+
async get(key) {
|
|
341
|
+
try {
|
|
342
|
+
return await this.redis.get(key);
|
|
343
|
+
} catch (error) {
|
|
344
|
+
this.logger.warn('Cache read failed, falling back', { key, error });
|
|
345
|
+
return null; // Graceful fallback
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async set(key, value) {
|
|
350
|
+
try {
|
|
351
|
+
await this.redis.set(key, value);
|
|
352
|
+
} catch (error) {
|
|
353
|
+
this.logger.error('Cache write failed', { key, error });
|
|
354
|
+
// Don't throw - cache writes are not critical
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Timeout Handling
|
|
361
|
+
|
|
362
|
+
Set appropriate timeouts for long-running operations.
|
|
363
|
+
|
|
364
|
+
**Pattern:**
|
|
365
|
+
|
|
366
|
+
```javascript
|
|
367
|
+
async _start() {
|
|
368
|
+
const timeout = this.config.startTimeout || 10000;
|
|
369
|
+
|
|
370
|
+
const controller = new AbortController();
|
|
371
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
372
|
+
|
|
373
|
+
try {
|
|
374
|
+
this.connection = await fetch(this.endpoint, {
|
|
375
|
+
signal: controller.signal
|
|
376
|
+
});
|
|
377
|
+
} finally {
|
|
378
|
+
clearTimeout(timeoutId);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
## Dependency Management Patterns
|
|
384
|
+
|
|
385
|
+
### Declare Dependencies Explicitly
|
|
386
|
+
|
|
387
|
+
Always declare service dependencies for proper startup ordering.
|
|
388
|
+
|
|
389
|
+
**Pattern:**
|
|
390
|
+
|
|
391
|
+
```javascript
|
|
392
|
+
// Register services with explicit dependencies
|
|
393
|
+
manager.register('database', DatabaseService, dbConfig);
|
|
394
|
+
|
|
395
|
+
manager.register(
|
|
396
|
+
'cache',
|
|
397
|
+
CacheService,
|
|
398
|
+
cacheConfig,
|
|
399
|
+
{
|
|
400
|
+
dependencies: ['database'] // Cache needs database
|
|
401
|
+
}
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
manager.register(
|
|
405
|
+
'auth',
|
|
406
|
+
AuthService,
|
|
407
|
+
authConfig,
|
|
408
|
+
{
|
|
409
|
+
dependencies: ['database', 'cache'] // Auth needs both
|
|
410
|
+
}
|
|
411
|
+
);
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Startup Priority
|
|
415
|
+
|
|
416
|
+
Use priority for services that should start early.
|
|
417
|
+
|
|
418
|
+
**Pattern:**
|
|
419
|
+
|
|
420
|
+
```javascript
|
|
421
|
+
// Start logger first (highest priority)
|
|
422
|
+
manager.register(
|
|
423
|
+
'logger',
|
|
424
|
+
LoggerService,
|
|
425
|
+
logConfig,
|
|
426
|
+
{ startupPriority: 100 }
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
// Then database (high priority)
|
|
430
|
+
manager.register(
|
|
431
|
+
'database',
|
|
432
|
+
DatabaseService,
|
|
433
|
+
dbConfig,
|
|
434
|
+
{ startupPriority: 50 }
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
// Then other services (default priority: 0)
|
|
438
|
+
manager.register('auth', AuthService, authConfig);
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
## Best Practices
|
|
442
|
+
|
|
443
|
+
### Service Design
|
|
444
|
+
|
|
445
|
+
1. **Always extend ServiceBase** for consistent lifecycle management
|
|
446
|
+
2. **Keep configuration lightweight** - heavy work should be in
|
|
447
|
+
`_start()`
|
|
448
|
+
3. **Implement proper cleanup** in `_stop()` to prevent resource leaks
|
|
449
|
+
4. **Use health checks** for monitoring critical service functionality
|
|
450
|
+
5. **Handle errors gracefully** and implement recovery where
|
|
451
|
+
appropriate
|
|
452
|
+
|
|
453
|
+
### Service Registration
|
|
454
|
+
|
|
455
|
+
6. **Declare dependencies explicitly** when registering with
|
|
456
|
+
ServiceManager
|
|
457
|
+
7. **Use descriptive service names** for better logging and debugging
|
|
458
|
+
8. **Set appropriate priorities** for services with ordering
|
|
459
|
+
requirements
|
|
460
|
+
|
|
461
|
+
### Service Implementation
|
|
462
|
+
|
|
463
|
+
9. **Store service access as private methods** using hash prefix
|
|
464
|
+
10. **Return useful metrics** from health checks
|
|
465
|
+
11. **Implement intelligent reconfiguration** that applies minimal
|
|
466
|
+
changes
|
|
467
|
+
12. **Handle missing config gracefully** with sensible defaults
|
|
468
|
+
|
|
469
|
+
### Resource Management
|
|
470
|
+
|
|
471
|
+
13. **Clean up all resources** in `_stop()` (timers, connections,
|
|
472
|
+
listeners)
|
|
473
|
+
14. **Set appropriate timeouts** for long-running operations
|
|
474
|
+
15. **Use graceful degradation** instead of throwing errors when
|
|
475
|
+
possible
|
|
476
|
+
16. **Test service lifecycle** (start, stop, restart, reconfigure)
|