@owlmeans/server-wl 0.1.2 → 0.1.4

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 CHANGED
@@ -1,800 +1,52 @@
1
1
  # @owlmeans/server-wl
2
2
 
3
- Server-side whitelabeling functionality for OwlMeans Common Libraries. This package provides backend whitelabeling capabilities enabling server applications to deliver customized branding, theming, and content personalization based on entity-specific configurations.
3
+ Server-side whitelabeling provides entity-specific branding and configuration via backend modules.
4
4
 
5
5
  ## Overview
6
6
 
7
- The `@owlmeans/server-wl` package serves as the server-side implementation of the OwlMeans Whitelabeling Subsystem, designed for fullstack applications with focus on security and dynamic brand customization. It provides:
8
-
9
- - **Server-side Whitelabeling**: Backend whitelabeling provider services and API endpoints
10
- - **Entity-based Branding**: Dynamic brand customization based on entity identification
11
- - **Multi-provider Architecture**: Support for multiple whitelabeling data providers (company info, styles, media, DNS)
12
- - **API Integration**: RESTful endpoints for whitelabeling data retrieval
13
- - **Identifier Resolution**: DNS and domain-based entity identification services
14
- - **Security Integration**: Secure whitelabeling data access with OwlMeans authentication
15
-
16
- This package follows the OwlMeans "quadra" pattern as the **server** implementation, complementing:
17
- - **@owlmeans/wled**: Common whitelabeling declarations and base functionality *(base package)*
18
- - **@owlmeans/client-wl**: Client-side whitelabeling implementation
19
- - **@owlmeans/web-wl**: Web-specific whitelabeling implementation
20
- - **@owlmeans/server-wl**: Server-side whitelabeling implementation *(this package)*
7
+ - Exports `modules` array: pre-built server modules that serve WL (whitelabel) configuration to clients
8
+ - Provides `provide` action — the handler for the WL configuration endpoint
9
+ - Integrates with `@owlmeans/wled` for whitelabel data definitions
10
+ - Apps add WL DNS service from `@owlmeans/server-wl-dns` for domain-based entity resolution
21
11
 
22
12
  ## Installation
23
13
 
24
14
  ```bash
25
- npm install @owlmeans/server-wl
26
- ```
27
-
28
- ## Dependencies
29
-
30
- This package requires and integrates with:
31
- - `@owlmeans/wled`: Core whitelabeling types and modules
32
- - `@owlmeans/server-context`: Server context management
33
- - `@owlmeans/server-module`: Server module system for whitelabeling APIs
34
- - `@owlmeans/server-api`: Server API utilities for request handling
35
- - `@owlmeans/context`: Context management and service registration
36
-
37
- ## Key Concepts
38
-
39
- ### Whitelabeling Providers
40
- The package implements a provider pattern where different aspects of whitelabeling can be handled by separate services:
41
-
42
- - **Company Information Provider**: Provides company details, branding information
43
- - **Styles Provider**: Delivers custom colors, fonts, and visual styling
44
- - **Media Provider**: Serves custom logos, images, and brand assets
45
- - **DNS Provider**: Handles domain-based entity identification
46
-
47
- ### Entity Identification
48
- Server-side entity identification enables:
49
- - **Domain Resolution**: Map domains/hostnames to specific entities
50
- - **Subdomain Routing**: Route based on subdomain patterns
51
- - **Custom Identifiers**: Support for custom entity identification schemes
52
- - **Fallback Handling**: Graceful handling of unrecognized entities
53
-
54
- ### API Integration
55
- Provides RESTful endpoints for:
56
- - **Whitelabeling Data Retrieval**: Get entity-specific branding data
57
- - **Real-time Updates**: Dynamic branding updates without deployment
58
- - **Caching Support**: Efficient caching of whitelabeling data
59
- - **Validation**: Server-side validation of whitelabeling configurations
60
-
61
- ## API Reference
62
-
63
- ### Core Interfaces
64
-
65
- #### `WlProvider`
66
-
67
- Service interface for whitelabeling data providers.
68
-
69
- ```typescript
70
- interface WlProvider extends InitializedService {
71
- provide: (entityId: string) => Promise<ProvidedWL>
72
- }
73
- ```
74
-
75
- **Methods:**
76
-
77
- **`provide(entityId: string): Promise<ProvidedWL>`**
78
- - **Purpose**: Provide whitelabeling data for a specific entity
79
- - **Parameters**: `entityId` - Unique entity identifier
80
- - **Returns**: Promise resolving to whitelabeling data object
81
- - **Behavior**: Retrieves and returns entity-specific whitelabeling configuration
82
-
83
- #### `WlEntityIdentifier`
84
-
85
- Service interface for entity identification.
86
-
87
- ```typescript
88
- interface WlEntityIdentifier extends InitializedService {
89
- identifyEntity: (identifier: string) => Promise<string | null>
90
- }
91
- ```
92
-
93
- **Methods:**
94
-
95
- **`identifyEntity(identifier: string): Promise<string | null>`**
96
- - **Purpose**: Identify entity from domain, hostname, or custom identifier
97
- - **Parameters**: `identifier` - Domain, hostname, or custom identifier string
98
- - **Returns**: Promise resolving to entity ID or null if not found
99
- - **Behavior**: Maps identifier to entity ID using configured resolution strategy
100
-
101
- #### `WlProviderAppend`
102
-
103
- Configuration interface for whitelabeling provider setup.
104
-
105
- ```typescript
106
- interface WlProviderAppend {
107
- wlProviders: string[] // Array of whitelabeling provider service names
108
- wlIdentifierService?: string // Optional entity identifier service name
109
- }
110
- ```
111
-
112
- #### `Config`
113
-
114
- Server configuration interface extending base server config with whitelabeling settings.
115
-
116
- ```typescript
117
- interface Config extends ServerConfig, WlProviderAppend {
118
- // Inherits all server configuration options
119
- // Plus whitelabeling-specific configuration
120
- }
121
- ```
122
-
123
- #### `Context<C extends Config = Config>`
124
-
125
- Server context interface with whitelabeling support.
126
-
127
- ```typescript
128
- interface Context<C extends Config = Config> extends ServerContext<C> {
129
- // Inherits all server context functionality
130
- // With typed configuration for whitelabeling
131
- }
132
- ```
133
-
134
- ### Module Integration
135
-
136
- The package automatically elevates base whitelabeling modules with server-specific handlers:
137
-
138
- ```typescript
139
- import { elevate } from '@owlmeans/server-module'
140
- import { WL_PROVIDE, modules as wlModules } from '@owlmeans/wled'
141
- import * as actions from './actions/index.js'
142
-
143
- // Elevate base modules with server handlers
144
- elevate(wlModules, WL_PROVIDE, actions.provide)
145
-
146
- export const modules = wlModules as ServerModule<unknown>[]
147
- ```
148
-
149
- ### Server Actions
150
-
151
- #### `provide` Action Handler
152
-
153
- Server-side handler for whitelabeling data provision requests.
154
-
155
- ```typescript
156
- const provide: RefedModuleHandler = handleParams<ProvideParams>(
157
- async (params, ctx) => {
158
- const context = assertContext(ctx, 'provide') as Context
159
-
160
- // Optional DNS-based entity identification
161
- const dns = context.cfg.wlIdentifierService == null ? undefined
162
- : context.service<WlEntityIdentifier>(context.cfg.wlIdentifierService)
163
-
164
- // Resolve entity ID from identifier
165
- const entityId = dns != null
166
- ? await dns.identifyEntity(params.entity) ?? params.entity
167
- : params.entity
168
-
169
- // Collect data from all registered providers
170
- const wl = Object.fromEntries(await Promise.all(
171
- context.cfg.wlProviders.map(async provider => {
172
- const srv = context.service<WlProvider>(provider)
173
- return [provider, await srv.provide(entityId)]
174
- })
175
- ))
176
-
177
- return wl
178
- }
179
- )
180
- ```
181
-
182
- ## Usage Examples
183
-
184
- ### Basic Server Whitelabeling Setup
185
-
186
- ```typescript
187
- import { makeServerContext } from '@owlmeans/server-context'
188
- import { modules } from '@owlmeans/server-wl'
189
- import type { Config, Context, WlProvider } from '@owlmeans/server-wl'
190
-
191
- // Configure server with whitelabeling support
192
- const config: Config = {
193
- service: 'whitelabel-server',
194
- type: AppType.Backend,
195
- layer: Layer.Service,
196
- wlProviders: ['company-provider', 'styles-provider', 'media-provider'],
197
- wlIdentifierService: 'dns-identifier'
198
- }
199
-
200
- const context: Context = makeServerContext(config)
201
-
202
- // Register whitelabeling modules
203
- context.registerModules(modules)
204
-
205
- // Initialize context
206
- await context.configure().init()
207
- ```
208
-
209
- ### Company Information Provider
210
-
211
- ```typescript
212
- import { createService } from '@owlmeans/context'
213
- import type { WlProvider } from '@owlmeans/server-wl'
214
- import type { CompanyInfo, ProvidedWL } from '@owlmeans/wled'
215
-
216
- // Create company information provider
217
- const companyProvider = createService<WlProvider>('company-provider', {
218
- async provide(entityId: string): Promise<ProvidedWL<CompanyInfo>> {
219
- try {
220
- // Load company information from database
221
- const company = await loadCompanyFromDatabase(entityId)
222
-
223
- if (!company) {
224
- return {
225
- type: 'company',
226
- exists: false
227
- }
228
- }
229
-
230
- return {
231
- type: 'company',
232
- exists: true,
233
- entityId: company.id,
234
- fullName: company.fullName,
235
- shortName: company.shortName,
236
- slug: company.slug,
237
- description: company.description,
238
- resource: 'company-info'
239
- }
240
- } catch (error) {
241
- console.error(`Failed to load company info for entity ${entityId}:`, error)
242
- return {
243
- type: 'company',
244
- exists: null // null indicates error
245
- }
246
- }
247
- }
248
- }, (service) => async () => {
249
- // Initialize database connection
250
- await initializeDatabase()
251
- service.initialized = true
252
- })
253
-
254
- context.registerService(companyProvider)
255
-
256
- // Database loading function
257
- const loadCompanyFromDatabase = async (entityId: string): Promise<CompanyInfo | null> => {
258
- const query = 'SELECT * FROM companies WHERE entity_id = ?'
259
- const result = await database.query(query, [entityId])
260
- return result.rows[0] || null
261
- }
262
- ```
263
-
264
- ### Custom Styles Provider
265
-
266
- ```typescript
267
- import type { WlProvider } from '@owlmeans/server-wl'
268
- import type { CustomStyles, ProvidedWL } from '@owlmeans/wled'
269
-
270
- const stylesProvider = createService<WlProvider>('styles-provider', {
271
- async provide(entityId: string): Promise<ProvidedWL<CustomStyles>> {
272
- try {
273
- const styles = await loadStylesFromDatabase(entityId)
274
-
275
- if (!styles) {
276
- // Return default styles for unknown entities
277
- return {
278
- type: 'styles',
279
- exists: false,
280
- entityId,
281
- font: {
282
- fontFamily: 'Roboto',
283
- basicSize: 14
284
- },
285
- colors: {
286
- primaryColor: '#1976d2',
287
- secondaryColor: '#dc004e',
288
- primaryBackground: '#ffffff',
289
- secondaryBackground: '#f5f5f5'
290
- }
291
- }
292
- }
293
-
294
- return {
295
- type: 'styles',
296
- exists: true,
297
- ...styles
298
- }
299
- } catch (error) {
300
- console.error(`Failed to load styles for entity ${entityId}:`, error)
301
- return {
302
- type: 'styles',
303
- exists: null
304
- }
305
- }
306
- }
307
- })
308
-
309
- context.registerService(stylesProvider)
15
+ bun add @owlmeans/server-wl
310
16
  ```
311
17
 
312
- ### Media Assets Provider
18
+ ## Usage
313
19
 
314
- ```typescript
315
- import type { WlProvider } from '@owlmeans/server-wl'
316
- import type { CustomMedia, ProvidedWL } from '@owlmeans/wled'
317
-
318
- const mediaProvider = createService<WlProvider>('media-provider', {
319
- async provide(entityId: string): Promise<ProvidedWL<CustomMedia>> {
320
- try {
321
- const media = await loadMediaFromStorage(entityId)
322
-
323
- return {
324
- type: 'media',
325
- exists: media !== null,
326
- entityId,
327
- brand: {
328
- squareLogo: media?.squareLogo ? `/assets/${entityId}/square-logo.png` : undefined,
329
- wideLogo: media?.wideLogo ? `/assets/${entityId}/wide-logo.png` : undefined
330
- }
331
- }
332
- } catch (error) {
333
- console.error(`Failed to load media for entity ${entityId}:`, error)
334
- return {
335
- type: 'media',
336
- exists: null,
337
- entityId,
338
- brand: {}
339
- }
340
- }
341
- }
342
- })
343
-
344
- context.registerService(mediaProvider)
345
-
346
- const loadMediaFromStorage = async (entityId: string) => {
347
- // Check if media files exist in storage
348
- const squareLogoExists = await fileExists(`/storage/assets/${entityId}/square-logo.png`)
349
- const wideLogoExists = await fileExists(`/storage/assets/${entityId}/wide-logo.png`)
350
-
351
- if (!squareLogoExists && !wideLogoExists) {
352
- return null
353
- }
354
-
355
- return {
356
- squareLogo: squareLogoExists,
357
- wideLogo: wideLogoExists
358
- }
359
- }
360
- ```
361
-
362
- ### DNS-based Entity Identifier
363
-
364
- ```typescript
365
- import type { WlEntityIdentifier } from '@owlmeans/server-wl'
366
-
367
- const dnsIdentifier = createService<WlEntityIdentifier>('dns-identifier', {
368
- async identifyEntity(identifier: string): Promise<string | null> {
369
- try {
370
- // Handle different identifier patterns
371
-
372
- // Subdomain pattern: entity.example.com
373
- const subdomainMatch = identifier.match(/^([a-zA-Z0-9-]+)\.example\.com$/)
374
- if (subdomainMatch) {
375
- const entitySlug = subdomainMatch[1]
376
- return await resolveEntityBySlug(entitySlug)
377
- }
378
-
379
- // Custom domain pattern
380
- const customDomain = await resolveCustomDomain(identifier)
381
- if (customDomain) {
382
- return customDomain.entityId
383
- }
384
-
385
- // Direct entity ID
386
- if (await isValidEntityId(identifier)) {
387
- return identifier
388
- }
389
-
390
- return null
391
- } catch (error) {
392
- console.error(`Failed to identify entity for ${identifier}:`, error)
393
- return null
394
- }
395
- }
396
- })
397
-
398
- context.registerService(dnsIdentifier)
399
-
400
- const resolveEntityBySlug = async (slug: string): Promise<string | null> => {
401
- const query = 'SELECT entity_id FROM entities WHERE slug = ?'
402
- const result = await database.query(query, [slug])
403
- return result.rows[0]?.entity_id || null
404
- }
405
-
406
- const resolveCustomDomain = async (domain: string) => {
407
- const query = 'SELECT entity_id FROM custom_domains WHERE domain = ?'
408
- const result = await database.query(query, [domain])
409
- return result.rows[0] || null
410
- }
411
- ```
412
-
413
- ### Express.js Integration
414
-
415
- ```typescript
416
- import express from 'express'
417
- import { handleApiRequest } from '@owlmeans/server-api'
418
-
419
- const app = express()
420
-
421
- // Whitelabeling endpoint
422
- app.get('/api/whitelabel/:entity', async (req, res) => {
423
- try {
424
- const result = await handleApiRequest(context, 'wl-provide', {
425
- entity: req.params.entity
426
- })
427
-
428
- res.json(result)
429
- } catch (error) {
430
- console.error('Whitelabeling API error:', error)
431
- res.status(500).json({ error: 'Failed to load whitelabeling data' })
432
- }
433
- })
434
-
435
- // Middleware for entity identification from hostname
436
- app.use(async (req, res, next) => {
437
- const hostname = req.hostname
438
- const dnsService = context.service<WlEntityIdentifier>('dns-identifier')
439
-
440
- const entityId = await dnsService.identifyEntity(hostname)
441
- if (entityId) {
442
- req.entityId = entityId
443
- }
444
-
445
- next()
446
- })
447
-
448
- // Dynamic branding endpoint
449
- app.get('/api/branding', async (req, res) => {
450
- const entityId = req.entityId || req.query.entity as string
451
-
452
- if (!entityId) {
453
- return res.status(400).json({ error: 'Entity not specified' })
454
- }
455
-
456
- try {
457
- const brandingData = await getAllWhitelabelingData(entityId)
458
- res.json(brandingData)
459
- } catch (error) {
460
- res.status(500).json({ error: 'Failed to load branding data' })
461
- }
462
- })
463
-
464
- const getAllWhitelabelingData = async (entityId: string) => {
465
- const providers = ['company-provider', 'styles-provider', 'media-provider']
466
- const data = {}
467
-
468
- for (const providerName of providers) {
469
- const provider = context.service<WlProvider>(providerName)
470
- data[providerName] = await provider.provide(entityId)
471
- }
472
-
473
- return data
474
- }
475
- ```
476
-
477
- ### Caching Implementation
20
+ Register WL modules in a backend service:
478
21
 
479
22
  ```typescript
480
- import NodeCache from 'node-cache'
481
-
482
- // Cache whitelabeling data for performance
483
- const wlCache = new NodeCache({ stdTTL: 300 }) // 5 minutes TTL
23
+ import { modules as wlModules } from '@owlmeans/server-wl'
24
+ import { main, modules } from '@owlmeans/server-app'
484
25
 
485
- const cachedProvider = createService<WlProvider>('cached-company-provider', {
486
- async provide(entityId: string): Promise<ProvidedWL<CompanyInfo>> {
487
- const cacheKey = `company:${entityId}`
488
-
489
- // Check cache first
490
- const cached = wlCache.get<ProvidedWL<CompanyInfo>>(cacheKey)
491
- if (cached) {
492
- return cached
493
- }
494
-
495
- // Load from database
496
- const data = await originalProvider.provide(entityId)
497
-
498
- // Cache only successful results
499
- if (data.exists === true) {
500
- wlCache.set(cacheKey, data)
501
- }
502
-
503
- return data
504
- }
505
- })
506
-
507
- // Cache invalidation on updates
508
- const invalidateWhitelabelingCache = (entityId: string) => {
509
- const keys = wlCache.keys().filter(key => key.includes(entityId))
510
- keys.forEach(key => wlCache.del(key))
511
- }
26
+ await main(context, [...modules, ...wlModules, ...appModules])
512
27
  ```
513
28
 
514
- ### Multi-tenant Configuration
29
+ With DNS-based entity resolution (from viable):
515
30
 
516
31
  ```typescript
517
- // Multi-tenant server configuration
518
- const multiTenantConfig: Config = {
519
- service: 'multi-tenant-server',
520
- type: AppType.Backend,
521
- layer: Layer.System,
522
- wlProviders: [
523
- 'company-provider',
524
- 'styles-provider',
525
- 'media-provider',
526
- 'urls-provider'
527
- ],
528
- wlIdentifierService: 'multi-tenant-dns'
529
- }
32
+ import { appendWlDnsService } from '@owlmeans/server-wl-dns'
33
+ import { wlDnsModules } from '@owlmeans/server-wl-dns'
530
34
 
531
- // Advanced DNS identifier for multi-tenancy
532
- const multiTenantDns = createService<WlEntityIdentifier>('multi-tenant-dns', {
533
- async identifyEntity(identifier: string): Promise<string | null> {
534
- // Priority order for entity identification:
535
-
536
- // 1. Custom domain (highest priority)
537
- const customEntity = await resolveCustomDomain(identifier)
538
- if (customEntity) return customEntity.entityId
539
-
540
- // 2. Subdomain pattern
541
- const subdomainEntity = await resolveSubdomain(identifier)
542
- if (subdomainEntity) return subdomainEntity
543
-
544
- // 3. Path-based routing (from referrer or context)
545
- const pathEntity = await resolveFromPath(identifier)
546
- if (pathEntity) return pathEntity
547
-
548
- // 4. Default entity for main domain
549
- if (identifier === 'example.com') {
550
- return 'default-entity'
551
- }
552
-
553
- return null
554
- }
555
- })
35
+ appendWlDnsService(context)
36
+ await main(context, [...modules, ...wlDnsModules, ...appModules])
556
37
  ```
557
38
 
558
- ## Error Handling
559
-
560
- ### Provider Error Handling
39
+ ## API
561
40
 
562
- ```typescript
563
- import { ResilientError } from '@owlmeans/error'
41
+ ### `modules`
564
42
 
565
- // Custom whitelabeling errors
566
- export class WhitelabelingProviderError extends ResilientError {
567
- constructor(provider: string, entityId: string, originalError: Error) {
568
- super('WL_PROVIDER_ERROR', `Whitelabeling provider ${provider} failed for entity ${entityId}`, {
569
- provider, entityId, originalError: originalError.message
570
- })
571
- }
572
- }
43
+ Array of `ServerModule` instances providing the WL configuration API endpoint.
573
44
 
574
- export class EntityNotFoundError extends ResilientError {
575
- constructor(identifier: string) {
576
- super('WL_ENTITY_NOT_FOUND', `Entity not found for identifier: ${identifier}`, {
577
- identifier
578
- })
579
- }
580
- }
581
-
582
- // Error-resilient provider wrapper
583
- const resilientProvider = (baseProvider: WlProvider, providerName: string): WlProvider => ({
584
- ...baseProvider,
585
- async provide(entityId: string): Promise<ProvidedWL> {
586
- try {
587
- return await baseProvider.provide(entityId)
588
- } catch (error) {
589
- console.error(`Provider ${providerName} failed for entity ${entityId}:`, error)
590
-
591
- // Return error state instead of throwing
592
- return {
593
- type: providerName,
594
- exists: null, // null indicates error
595
- error: error.message
596
- }
597
- }
598
- }
599
- })
600
- ```
45
+ ### `WlConfig` / `WlRecord` (types)
601
46
 
602
- ### Graceful Degradation
603
-
604
- ```typescript
605
- // Graceful degradation for missing providers
606
- const handleWhitelabelingRequest = async (entityId: string) => {
607
- const results = {}
608
- const errors = []
609
-
610
- for (const providerName of config.wlProviders) {
611
- try {
612
- const provider = context.service<WlProvider>(providerName)
613
- results[providerName] = await provider.provide(entityId)
614
- } catch (error) {
615
- errors.push({ provider: providerName, error: error.message })
616
-
617
- // Provide default/fallback data
618
- results[providerName] = getDefaultWhitelabelingData(providerName, entityId)
619
- }
620
- }
621
-
622
- return {
623
- data: results,
624
- errors: errors.length > 0 ? errors : undefined,
625
- success: errors.length === 0
626
- }
627
- }
628
- ```
629
-
630
- ## Security Considerations
631
-
632
- ### Access Control
633
-
634
- ```typescript
635
- // Secure whitelabeling data access
636
- const secureProvider = (baseProvider: WlProvider): WlProvider => ({
637
- ...baseProvider,
638
- async provide(entityId: string): Promise<ProvidedWL> {
639
- // Validate entity ID format
640
- if (!isValidEntityId(entityId)) {
641
- throw new Error('Invalid entity ID format')
642
- }
643
-
644
- // Check access permissions
645
- if (!await hasWhitelabelingAccess(entityId)) {
646
- throw new Error('Insufficient permissions for whitelabeling data')
647
- }
648
-
649
- return baseProvider.provide(entityId)
650
- }
651
- })
652
-
653
- const isValidEntityId = (entityId: string): boolean => {
654
- return /^[a-zA-Z0-9-_]{1,50}$/.test(entityId)
655
- }
656
-
657
- const hasWhitelabelingAccess = async (entityId: string): Promise<boolean> => {
658
- // Implement access control logic
659
- return true // Placeholder
660
- }
661
- ```
662
-
663
- ### Data Validation
664
-
665
- ```typescript
666
- import { validate } from 'ajv'
667
- import { CompanyInfoSchema, CustomStylesSchema } from '@owlmeans/wled'
668
-
669
- // Validate whitelabeling data before serving
670
- const validateWhitelabelingData = (data: any, type: string): boolean => {
671
- switch (type) {
672
- case 'company':
673
- return validate(CompanyInfoSchema, data)
674
- case 'styles':
675
- return validate(CustomStylesSchema, data)
676
- default:
677
- return true
678
- }
679
- }
680
- ```
681
-
682
- ## Performance Optimization
683
-
684
- ### Connection Pooling
685
-
686
- ```typescript
687
- // Database connection pooling for providers
688
- import { Pool } from 'pg'
689
-
690
- const dbPool = new Pool({
691
- connectionString: process.env.DATABASE_URL,
692
- max: 20,
693
- idleTimeoutMillis: 30000,
694
- connectionTimeoutMillis: 2000
695
- })
696
-
697
- const optimizedProvider = createService<WlProvider>('optimized-provider', {
698
- async provide(entityId: string): Promise<ProvidedWL> {
699
- const client = await dbPool.connect()
700
- try {
701
- const result = await client.query('SELECT * FROM whitelabeling WHERE entity_id = $1', [entityId])
702
- return formatWhitelabelingData(result.rows[0])
703
- } finally {
704
- client.release()
705
- }
706
- }
707
- })
708
- ```
709
-
710
- ### Batch Loading
711
-
712
- ```typescript
713
- // Batch loading for multiple entities
714
- const batchProvider = createService<WlProvider>('batch-provider', {
715
- private cache: Map<string, Promise<ProvidedWL>> = new Map()
716
-
717
- async provide(entityId: string): Promise<ProvidedWL> {
718
- // Return existing promise if already loading
719
- if (this.cache.has(entityId)) {
720
- return this.cache.get(entityId)!
721
- }
722
-
723
- // Create new loading promise
724
- const promise = this.loadWhitelabelingData(entityId)
725
- this.cache.set(entityId, promise)
726
-
727
- // Clean up cache after resolution
728
- promise.finally(() => {
729
- setTimeout(() => this.cache.delete(entityId), 60000) // 1 minute cleanup
730
- })
731
-
732
- return promise
733
- },
734
-
735
- private async loadWhitelabelingData(entityId: string): Promise<ProvidedWL> {
736
- // Implementation
737
- }
738
- })
739
- ```
740
-
741
- ## Integration with OwlMeans Ecosystem
742
-
743
- ### Context Integration
744
- ```typescript
745
- import { makeServerContext } from '@owlmeans/server-context'
746
-
747
- const context = makeServerContext(config)
748
- const wlProvider = context.service<WlProvider>('company-provider')
749
- ```
750
-
751
- ### Module System Integration
752
- ```typescript
753
- import { modules } from '@owlmeans/server-wl'
754
-
755
- // Register whitelabeling modules
756
- context.registerModules(modules)
757
- ```
758
-
759
- ### API Integration
760
- ```typescript
761
- import { handleApiRequest } from '@owlmeans/server-api'
762
-
763
- // Handle whitelabeling API requests
764
- const result = await handleApiRequest(context, 'wl-provide', { entity: entityId })
765
- ```
766
-
767
- ## Best Practices
768
-
769
- 1. **Provider Design**: Keep providers focused and single-responsibility
770
- 2. **Caching Strategy**: Implement appropriate caching with TTL for performance
771
- 3. **Error Handling**: Provide graceful degradation for missing data
772
- 4. **Security**: Validate entity IDs and implement access controls
773
- 5. **Performance**: Use connection pooling and batch loading where appropriate
774
- 6. **Monitoring**: Log provider performance and error rates
775
- 7. **Fallbacks**: Always provide sensible defaults for missing configurations
47
+ Whitelabel configuration types defining branding, theme, and entity-specific settings.
776
48
 
777
49
  ## Related Packages
778
50
 
779
- - **@owlmeans/wled**: Core whitelabeling types and modules
780
- - **@owlmeans/client-wl**: Client-side whitelabeling implementation
781
- - **@owlmeans/web-wl**: Web-specific whitelabeling implementation
782
- - **@owlmeans/server-context**: Server context management
783
- - **@owlmeans/server-module**: Server module system
784
- - **@owlmeans/server-api**: Server API utilities
785
-
786
- ## TypeScript Support
787
-
788
- This package is written in TypeScript and provides full type safety:
789
-
790
- ```typescript
791
- import type {
792
- WlProvider,
793
- WlEntityIdentifier,
794
- Config,
795
- Context
796
- } from '@owlmeans/server-wl'
797
-
798
- const provider: WlProvider = createService('my-provider', { /* ... */ })
799
- const context: Context = makeServerContext(config)
800
- ```
51
+ - [`@owlmeans/client-wl`](../client-wl) client-side WL service that fetches from this server
52
+ - [`@owlmeans/server-app`](../server-app) server bootstrap that includes these modules
@@ -1,3 +1,3 @@
1
- import type { RefedModuleHandler } from '@owlmeans/server-module';
2
- export declare const provide: RefedModuleHandler;
1
+ import type { RefedEntrypointHandler } from '@owlmeans/server-entrypoint';
2
+ export declare const provide: RefedEntrypointHandler;
3
3
  //# sourceMappingURL=provide.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"provide.d.ts","sourceRoot":"","sources":["../../src/actions/provide.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAMjE,eAAO,MAAM,OAAO,EAAE,kBAqBrB,CAAA"}
1
+ {"version":3,"file":"provide.d.ts","sourceRoot":"","sources":["../../src/actions/provide.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AAMzE,eAAO,MAAM,OAAO,EAAE,sBAqBrB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"provide.js","sourceRoot":"","sources":["../../src/actions/provide.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAEnD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAGjD,MAAM,CAAC,MAAM,OAAO,GAAuB,YAAY,CACrD,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE;IACpB,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,EAAE,SAAS,CAAY,CAAA;IAExD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS;QAC7D,CAAC,CAAC,OAAO,CAAC,OAAO,CAAqB,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAA;IAExE,MAAM,QAAQ,GAAG,GAAG,IAAI,IAAI;QAC1B,CAAC,CAAC,MAAM,GAAG,CAAC,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM;QAC1D,CAAC,CAAC,MAAM,CAAC,MAAM,CAAA;IAEjB,MAAM,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,OAAO,CAAC,GAAG,CAC7C,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,EAAC,QAAQ,EAAC,EAAE;QAC3C,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAa,QAAQ,CAAC,CAAA;QAEjD,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAA;IAChD,CAAC,CAAC,CACH,CAAC,CAAA;IAEF,OAAO,EAAE,CAAA;AACX,CAAC,CACF,CAAA"}
1
+ {"version":3,"file":"provide.js","sourceRoot":"","sources":["../../src/actions/provide.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAEnD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAGjD,MAAM,CAAC,MAAM,OAAO,GAA2B,YAAY,CACzD,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE;IACpB,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,EAAE,SAAS,CAAY,CAAA;IAExD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS;QAC7D,CAAC,CAAC,OAAO,CAAC,OAAO,CAAqB,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAA;IAExE,MAAM,QAAQ,GAAG,GAAG,IAAI,IAAI;QAC1B,CAAC,CAAC,MAAM,GAAG,CAAC,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM;QAC1D,CAAC,CAAC,MAAM,CAAC,MAAM,CAAA;IAEjB,MAAM,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,OAAO,CAAC,GAAG,CAC7C,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,EAAC,QAAQ,EAAC,EAAE;QAC3C,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAa,QAAQ,CAAC,CAAA;QAEjD,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAA;IAChD,CAAC,CAAC,CACH,CAAC,CAAA;IAEF,OAAO,EAAE,CAAA;AACX,CAAC,CACF,CAAA"}
@@ -1,3 +1,3 @@
1
- import type { ServerModule } from '@owlmeans/server-module';
2
- export declare const modules: ServerModule<unknown>[];
1
+ import type { ServerEntrypoint } from '@owlmeans/server-entrypoint';
2
+ export declare const modules: ServerEntrypoint<unknown>[];
3
3
  //# sourceMappingURL=modules.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"modules.d.ts","sourceRoot":"","sources":["../src/modules.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAM3D,eAAO,MAAM,OAAO,EAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,CAAA"}
1
+ {"version":3,"file":"modules.d.ts","sourceRoot":"","sources":["../src/modules.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAMnE,eAAO,MAAM,OAAO,EAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAA"}
package/build/modules.js CHANGED
@@ -1,4 +1,4 @@
1
- import { elevate } from '@owlmeans/server-module';
1
+ import { elevate } from '@owlmeans/server-entrypoint';
2
2
  import { WL_PROVIDE, modules as wlModules } from '@owlmeans/wled';
3
3
  import * as actions from './actions/index.js';
4
4
  elevate(wlModules, WL_PROVIDE, actions.provide);
@@ -1 +1 @@
1
- {"version":3,"file":"modules.js","sourceRoot":"","sources":["../src/modules.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAA;AAEjD,OAAO,EAAE,UAAU,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,gBAAgB,CAAA;AACjE,OAAO,KAAK,OAAO,MAAM,oBAAoB,CAAA;AAE7C,OAAO,CAAC,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;AAE/C,MAAM,CAAC,MAAM,OAAO,GAAG,SAAoC,CAAA"}
1
+ {"version":3,"file":"modules.js","sourceRoot":"","sources":["../src/modules.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAA;AAErD,OAAO,EAAE,UAAU,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,gBAAgB,CAAA;AACjE,OAAO,KAAK,OAAO,MAAM,oBAAoB,CAAA;AAE7C,OAAO,CAAC,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;AAE/C,MAAM,CAAC,MAAM,OAAO,GAAG,SAAwC,CAAA"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@owlmeans/server-wl",
3
3
  "type": "module",
4
- "version": "0.1.2",
4
+ "version": "0.1.4",
5
5
  "license": "MIT",
6
6
  "scripts": {
7
7
  "build": "tsc -b",
@@ -21,16 +21,17 @@
21
21
  }
22
22
  },
23
23
  "dependencies": {
24
- "@owlmeans/context": "^0.1.2",
25
- "@owlmeans/server-api": "^0.1.2",
26
- "@owlmeans/server-context": "^0.1.2",
27
- "@owlmeans/server-module": "^0.1.2",
28
- "@owlmeans/wled": "^0.1.2"
24
+ "@owlmeans/context": "^0.1.4",
25
+ "@owlmeans/server-api": "^0.1.4",
26
+ "@owlmeans/server-context": "^0.1.4",
27
+ "@owlmeans/server-entrypoint": "^0.1.4",
28
+ "@owlmeans/wled": "^0.1.4"
29
29
  },
30
30
  "devDependencies": {
31
+ "@owlmeans/dep-config": "workspace:*",
31
32
  "@types/node": "^24.10.1",
32
33
  "nodemon": "^3.1.11",
33
- "typescript": "^5.8.3"
34
+ "typescript": "^6.0.2"
34
35
  },
35
36
  "publishConfig": {
36
37
  "access": "public"
@@ -1,10 +1,10 @@
1
- import type { RefedModuleHandler } from '@owlmeans/server-module'
1
+ import type { RefedEntrypointHandler } from '@owlmeans/server-entrypoint'
2
2
  import { handleParams } from '@owlmeans/server-api'
3
3
  import type { Context, WlEntityIdentifier, WlProvider } from '../types.js'
4
4
  import { assertContext } from '@owlmeans/context'
5
5
  import type { ProvideParams } from '@owlmeans/wled'
6
6
 
7
- export const provide: RefedModuleHandler = handleParams<ProvideParams>(
7
+ export const provide: RefedEntrypointHandler = handleParams<ProvideParams>(
8
8
  async (params, ctx) => {
9
9
  const context = assertContext(ctx, 'provide') as Context
10
10
 
package/src/modules.ts CHANGED
@@ -1,9 +1,9 @@
1
1
 
2
- import { elevate } from '@owlmeans/server-module'
3
- import type { ServerModule } from '@owlmeans/server-module'
2
+ import { elevate } from '@owlmeans/server-entrypoint'
3
+ import type { ServerEntrypoint } from '@owlmeans/server-entrypoint'
4
4
  import { WL_PROVIDE, modules as wlModules } from '@owlmeans/wled'
5
5
  import * as actions from './actions/index.js'
6
6
 
7
7
  elevate(wlModules, WL_PROVIDE, actions.provide)
8
8
 
9
- export const modules = wlModules as ServerModule<unknown>[]
9
+ export const modules = wlModules as ServerEntrypoint<unknown>[]
package/tsconfig.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "extends": [
3
- "../tsconfig.default.json",
3
+ "@owlmeans/dep-config/tsconfig.base.json",
4
+ "@owlmeans/dep-config/tsconfig.node.json"
4
5
  ],
5
6
  "compilerOptions": {
6
- "rootDir": "./src/", /* Specify the root folder within your source files. */
7
- "outDir": "./build/", /* Specify an output folder for all emitted files. */
8
- "moduleResolution": "Bundler",
7
+ "rootDir": "./src/",
8
+ "outDir": "./build/"
9
9
  },
10
10
  "exclude": [
11
11
  "./dist/**/*",