@qwickapps/server 1.7.1 → 1.7.2

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.
Files changed (157) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/src/plugins/api-keys/api-keys-plugin.d.ts +5 -2
  3. package/dist/src/plugins/api-keys/api-keys-plugin.d.ts.map +1 -1
  4. package/dist/src/plugins/api-keys/api-keys-plugin.js +61 -19
  5. package/dist/src/plugins/api-keys/api-keys-plugin.js.map +1 -1
  6. package/dist/src/plugins/api-keys/index.d.ts +0 -4
  7. package/dist/src/plugins/api-keys/index.d.ts.map +1 -1
  8. package/dist/src/plugins/api-keys/index.js +2 -3
  9. package/dist/src/plugins/api-keys/index.js.map +1 -1
  10. package/dist/src/plugins/api-keys/types.d.ts +9 -3
  11. package/dist/src/plugins/api-keys/types.d.ts.map +1 -1
  12. package/dist/src/plugins/api-keys/types.js.map +1 -1
  13. package/dist/src/plugins/auth/index.d.ts +0 -4
  14. package/dist/src/plugins/auth/index.d.ts.map +1 -1
  15. package/dist/src/plugins/auth/index.js +2 -3
  16. package/dist/src/plugins/auth/index.js.map +1 -1
  17. package/dist/src/plugins/bans/bans-plugin.d.ts +5 -2
  18. package/dist/src/plugins/bans/bans-plugin.d.ts.map +1 -1
  19. package/dist/src/plugins/bans/bans-plugin.js +71 -25
  20. package/dist/src/plugins/bans/bans-plugin.js.map +1 -1
  21. package/dist/src/plugins/bans/index.d.ts +0 -4
  22. package/dist/src/plugins/bans/index.d.ts.map +1 -1
  23. package/dist/src/plugins/bans/index.js +2 -3
  24. package/dist/src/plugins/bans/index.js.map +1 -1
  25. package/dist/src/plugins/bans/types.d.ts +13 -6
  26. package/dist/src/plugins/bans/types.d.ts.map +1 -1
  27. package/dist/src/plugins/devices/devices-plugin.d.ts +5 -2
  28. package/dist/src/plugins/devices/devices-plugin.d.ts.map +1 -1
  29. package/dist/src/plugins/devices/devices-plugin.js +62 -26
  30. package/dist/src/plugins/devices/devices-plugin.js.map +1 -1
  31. package/dist/src/plugins/devices/index.d.ts +0 -4
  32. package/dist/src/plugins/devices/index.d.ts.map +1 -1
  33. package/dist/src/plugins/devices/index.js +2 -3
  34. package/dist/src/plugins/devices/index.js.map +1 -1
  35. package/dist/src/plugins/entitlements/entitlements-plugin.d.ts +5 -2
  36. package/dist/src/plugins/entitlements/entitlements-plugin.d.ts.map +1 -1
  37. package/dist/src/plugins/entitlements/entitlements-plugin.js +78 -41
  38. package/dist/src/plugins/entitlements/entitlements-plugin.js.map +1 -1
  39. package/dist/src/plugins/entitlements/index.d.ts +0 -4
  40. package/dist/src/plugins/entitlements/index.d.ts.map +1 -1
  41. package/dist/src/plugins/entitlements/index.js +2 -3
  42. package/dist/src/plugins/entitlements/index.js.map +1 -1
  43. package/dist/src/plugins/entitlements/types.d.ts +9 -2
  44. package/dist/src/plugins/entitlements/types.d.ts.map +1 -1
  45. package/dist/src/plugins/notifications/index.d.ts +0 -4
  46. package/dist/src/plugins/notifications/index.d.ts.map +1 -1
  47. package/dist/src/plugins/notifications/index.js +2 -3
  48. package/dist/src/plugins/notifications/index.js.map +1 -1
  49. package/dist/src/plugins/notifications/notifications-plugin.d.ts +5 -2
  50. package/dist/src/plugins/notifications/notifications-plugin.d.ts.map +1 -1
  51. package/dist/src/plugins/notifications/notifications-plugin.js +45 -13
  52. package/dist/src/plugins/notifications/notifications-plugin.js.map +1 -1
  53. package/dist/src/plugins/parental/index.d.ts +0 -4
  54. package/dist/src/plugins/parental/index.d.ts.map +1 -1
  55. package/dist/src/plugins/parental/index.js +2 -3
  56. package/dist/src/plugins/parental/index.js.map +1 -1
  57. package/dist/src/plugins/parental/parental-plugin.d.ts +5 -2
  58. package/dist/src/plugins/parental/parental-plugin.d.ts.map +1 -1
  59. package/dist/src/plugins/parental/parental-plugin.js +60 -24
  60. package/dist/src/plugins/parental/parental-plugin.js.map +1 -1
  61. package/dist/src/plugins/preferences/index.d.ts +0 -4
  62. package/dist/src/plugins/preferences/index.d.ts.map +1 -1
  63. package/dist/src/plugins/preferences/index.js +2 -3
  64. package/dist/src/plugins/preferences/index.js.map +1 -1
  65. package/dist/src/plugins/preferences/preferences-plugin.d.ts +5 -2
  66. package/dist/src/plugins/preferences/preferences-plugin.d.ts.map +1 -1
  67. package/dist/src/plugins/preferences/preferences-plugin.js +63 -19
  68. package/dist/src/plugins/preferences/preferences-plugin.js.map +1 -1
  69. package/dist/src/plugins/profiles/index.d.ts +0 -4
  70. package/dist/src/plugins/profiles/index.d.ts.map +1 -1
  71. package/dist/src/plugins/profiles/index.js +2 -3
  72. package/dist/src/plugins/profiles/index.js.map +1 -1
  73. package/dist/src/plugins/profiles/profiles-plugin.d.ts +5 -2
  74. package/dist/src/plugins/profiles/profiles-plugin.d.ts.map +1 -1
  75. package/dist/src/plugins/profiles/profiles-plugin.js +60 -26
  76. package/dist/src/plugins/profiles/profiles-plugin.js.map +1 -1
  77. package/dist/src/plugins/profiles/types.d.ts +9 -2
  78. package/dist/src/plugins/profiles/types.d.ts.map +1 -1
  79. package/dist/src/plugins/qwickbrain/index.d.ts +0 -4
  80. package/dist/src/plugins/qwickbrain/index.d.ts.map +1 -1
  81. package/dist/src/plugins/qwickbrain/index.js +2 -3
  82. package/dist/src/plugins/qwickbrain/index.js.map +1 -1
  83. package/dist/src/plugins/qwickbrain/qwickbrain-plugin.d.ts.map +1 -1
  84. package/dist/src/plugins/qwickbrain/qwickbrain-plugin.js +117 -0
  85. package/dist/src/plugins/qwickbrain/qwickbrain-plugin.js.map +1 -1
  86. package/dist/src/plugins/rate-limit/index.d.ts +0 -4
  87. package/dist/src/plugins/rate-limit/index.d.ts.map +1 -1
  88. package/dist/src/plugins/rate-limit/index.js +2 -3
  89. package/dist/src/plugins/rate-limit/index.js.map +1 -1
  90. package/dist/src/plugins/subscriptions/index.d.ts +0 -4
  91. package/dist/src/plugins/subscriptions/index.d.ts.map +1 -1
  92. package/dist/src/plugins/subscriptions/index.js +2 -3
  93. package/dist/src/plugins/subscriptions/index.js.map +1 -1
  94. package/dist/src/plugins/subscriptions/subscriptions-plugin.d.ts +5 -2
  95. package/dist/src/plugins/subscriptions/subscriptions-plugin.d.ts.map +1 -1
  96. package/dist/src/plugins/subscriptions/subscriptions-plugin.js +63 -29
  97. package/dist/src/plugins/subscriptions/subscriptions-plugin.js.map +1 -1
  98. package/dist/src/plugins/subscriptions/types.d.ts +8 -2
  99. package/dist/src/plugins/subscriptions/types.d.ts.map +1 -1
  100. package/dist/src/plugins/tenants/tenants-plugin.d.ts +5 -2
  101. package/dist/src/plugins/tenants/tenants-plugin.d.ts.map +1 -1
  102. package/dist/src/plugins/tenants/tenants-plugin.js +91 -58
  103. package/dist/src/plugins/tenants/tenants-plugin.js.map +1 -1
  104. package/dist/src/plugins/tenants/types.d.ts +8 -2
  105. package/dist/src/plugins/tenants/types.d.ts.map +1 -1
  106. package/dist/src/plugins/usage/index.d.ts +0 -4
  107. package/dist/src/plugins/usage/index.d.ts.map +1 -1
  108. package/dist/src/plugins/usage/index.js +2 -3
  109. package/dist/src/plugins/usage/index.js.map +1 -1
  110. package/dist/src/plugins/usage/usage-plugin.d.ts +5 -2
  111. package/dist/src/plugins/usage/usage-plugin.d.ts.map +1 -1
  112. package/dist/src/plugins/usage/usage-plugin.js +57 -23
  113. package/dist/src/plugins/usage/usage-plugin.js.map +1 -1
  114. package/dist/src/plugins/users/types.d.ts +7 -2
  115. package/dist/src/plugins/users/types.d.ts.map +1 -1
  116. package/dist/src/plugins/users/users-plugin.d.ts +5 -2
  117. package/dist/src/plugins/users/users-plugin.d.ts.map +1 -1
  118. package/dist/src/plugins/users/users-plugin.js +56 -23
  119. package/dist/src/plugins/users/users-plugin.js.map +1 -1
  120. package/dist-ui/assets/index-0gzisPdy.js +528 -0
  121. package/dist-ui/assets/{index-8y0jDGcd.js.map → index-0gzisPdy.js.map} +1 -1
  122. package/dist-ui/index.html +1 -1
  123. package/package.json +8 -5
  124. package/src/plugins/api-keys/api-keys-plugin.ts +64 -20
  125. package/src/plugins/api-keys/index.ts +2 -5
  126. package/src/plugins/api-keys/types.ts +9 -3
  127. package/src/plugins/auth/index.ts +3 -5
  128. package/src/plugins/bans/bans-plugin.ts +71 -26
  129. package/src/plugins/bans/index.ts +3 -5
  130. package/src/plugins/bans/types.ts +13 -6
  131. package/src/plugins/devices/devices-plugin.ts +62 -27
  132. package/src/plugins/devices/index.ts +3 -5
  133. package/src/plugins/entitlements/entitlements-plugin.ts +81 -43
  134. package/src/plugins/entitlements/index.ts +3 -5
  135. package/src/plugins/entitlements/types.ts +9 -2
  136. package/src/plugins/notifications/index.ts +3 -5
  137. package/src/plugins/notifications/notifications-plugin.ts +48 -19
  138. package/src/plugins/parental/index.ts +3 -5
  139. package/src/plugins/parental/parental-plugin.ts +63 -25
  140. package/src/plugins/preferences/index.ts +3 -5
  141. package/src/plugins/preferences/preferences-plugin.ts +66 -20
  142. package/src/plugins/profiles/index.ts +3 -5
  143. package/src/plugins/profiles/profiles-plugin.ts +60 -27
  144. package/src/plugins/profiles/types.ts +9 -2
  145. package/src/plugins/qwickbrain/index.ts +3 -5
  146. package/src/plugins/qwickbrain/qwickbrain-plugin.ts +135 -0
  147. package/src/plugins/rate-limit/index.ts +3 -5
  148. package/src/plugins/subscriptions/index.ts +3 -5
  149. package/src/plugins/subscriptions/subscriptions-plugin.ts +63 -30
  150. package/src/plugins/subscriptions/types.ts +8 -2
  151. package/src/plugins/tenants/tenants-plugin.ts +95 -60
  152. package/src/plugins/tenants/types.ts +8 -2
  153. package/src/plugins/usage/index.ts +3 -5
  154. package/src/plugins/usage/usage-plugin.ts +60 -26
  155. package/src/plugins/users/types.ts +7 -2
  156. package/src/plugins/users/users-plugin.ts +56 -24
  157. package/dist-ui/assets/index-8y0jDGcd.js +0 -528
@@ -28,6 +28,9 @@ import {
28
28
  isTokenExpired,
29
29
  getTokenExpiration,
30
30
  } from './token-utils.js';
31
+ import { hasPostgres, getPostgres } from '../postgres-plugin.js';
32
+ import { postgresDeviceStore } from './stores/index.js';
33
+ import { computeDeviceAdapter } from './adapters/index.js';
31
34
 
32
35
  // Store instances for helper access
33
36
  let currentStore: DeviceStore | null = null;
@@ -35,16 +38,18 @@ let currentAdapter: DeviceAdapter | null = null;
35
38
  let currentConfig: DevicesPluginConfig | null = null;
36
39
 
37
40
  /**
38
- * Create the Devices plugin
41
+ * Create the Devices plugin with smart defaults
42
+ *
43
+ * Config is optional - plugin will use defaults and get dependencies from registry.
44
+ * Gracefully handles missing dependencies with clear log messages.
39
45
  */
40
- export function createDevicesPlugin(config: DevicesPluginConfig): Plugin {
41
- const debug = config.debug || false;
42
- const defaultTokenValidityDays = config.defaultTokenValidityDays || 90;
43
- const apiPrefix = config.api?.prefix || '/'; // Framework adds /devices prefix automatically
44
-
45
- function log(message: string, data?: Record<string, unknown>) {
46
- if (debug) {
47
- console.log(`[DevicesPlugin] ${message}`, data || '');
46
+ export function createDevicesPlugin(config: Partial<DevicesPluginConfig> = {}): Plugin {
47
+ function log(message: string, data?: Record<string, unknown>, isError = false) {
48
+ const prefix = '[DevicesPlugin]';
49
+ if (isError) {
50
+ console.error(`${prefix} ${message}`, data || '');
51
+ } else if (config.debug) {
52
+ console.log(`${prefix} ${message}`, data || '');
48
53
  }
49
54
  }
50
55
 
@@ -54,16 +59,46 @@ export function createDevicesPlugin(config: DevicesPluginConfig): Plugin {
54
59
  version: '1.0.0',
55
60
 
56
61
  async onStart(_pluginConfig: PluginConfig, registry: PluginRegistry): Promise<void> {
57
- log('Starting devices plugin', { adapter: config.adapter.name });
62
+ const logger = registry.getLogger('devices');
63
+
64
+ // Check for postgres in registry
65
+ if (!hasPostgres()) {
66
+ logger.warn('No Database! Devices plugin disabled.');
67
+ registry.registerHealthCheck({
68
+ name: 'devices-store',
69
+ type: 'custom',
70
+ check: async () => ({
71
+ healthy: false,
72
+ details: {
73
+ error: 'PostgreSQL not available',
74
+ state: 'disabled',
75
+ },
76
+ }),
77
+ });
78
+ return;
79
+ }
80
+
81
+ // Smart defaults - get dependencies from registry
82
+ const store = config.store ?? postgresDeviceStore({
83
+ pool: () => getPostgres().getPool(),
84
+ autoCreateTables: true,
85
+ });
86
+
87
+ const adapter = config.adapter ?? computeDeviceAdapter();
88
+ const debug = config.debug ?? false;
89
+ const defaultTokenValidityDays = config.defaultTokenValidityDays ?? 90;
90
+ const apiPrefix = config.api?.prefix ?? '/devices';
91
+
92
+ log('Starting devices plugin', { adapter: adapter.name });
58
93
 
59
94
  // Initialize the store (creates tables if needed)
60
- await config.store.initialize();
95
+ await store.initialize();
61
96
  log('Devices plugin migrations complete');
62
97
 
63
98
  // Store references for helper access
64
- currentStore = config.store;
65
- currentAdapter = config.adapter;
66
- currentConfig = config;
99
+ currentStore = store;
100
+ currentAdapter = adapter;
101
+ currentConfig = { ...config, store, adapter, debug, defaultTokenValidityDays };
67
102
 
68
103
  // Register health check
69
104
  registry.registerHealthCheck({
@@ -71,12 +106,12 @@ export function createDevicesPlugin(config: DevicesPluginConfig): Plugin {
71
106
  type: 'custom',
72
107
  check: async () => {
73
108
  try {
74
- await config.store.search({ limit: 1 });
109
+ await store.search({ limit: 1 });
75
110
  return {
76
111
  healthy: true,
77
112
  details: {
78
- adapter: config.adapter.name,
79
- tokenPrefix: config.adapter.tokenPrefix,
113
+ adapter: adapter.name,
114
+ tokenPrefix: adapter.tokenPrefix,
80
115
  },
81
116
  };
82
117
  } catch {
@@ -106,7 +141,7 @@ export function createDevicesPlugin(config: DevicesPluginConfig): Plugin {
106
141
  sortOrder: (req.query.sortOrder as DeviceSearchParams['sortOrder']) || 'desc',
107
142
  };
108
143
 
109
- const result = await config.store.search(params);
144
+ const result = await store.search(params);
110
145
  res.json(result);
111
146
  } catch (error) {
112
147
  console.error('[DevicesPlugin] Search error:', error);
@@ -122,7 +157,7 @@ export function createDevicesPlugin(config: DevicesPluginConfig): Plugin {
122
157
  pluginId: 'devices',
123
158
  handler: async (req: Request, res: Response) => {
124
159
  try {
125
- const device = await config.store.getById(req.params.id);
160
+ const device = await store.getById(req.params.id);
126
161
  if (!device) {
127
162
  return res.status(404).json({ error: 'Device not found' });
128
163
  }
@@ -150,7 +185,7 @@ export function createDevicesPlugin(config: DevicesPluginConfig): Plugin {
150
185
  };
151
186
 
152
187
  // Validate using adapter
153
- const validation = config.adapter.validateDeviceInput(input);
188
+ const validation = adapter.validateDeviceInput(input);
154
189
  if (!validation.valid) {
155
190
  return res.status(400).json({
156
191
  error: 'Validation failed',
@@ -181,7 +216,7 @@ export function createDevicesPlugin(config: DevicesPluginConfig): Plugin {
181
216
  metadata: req.body.metadata,
182
217
  };
183
218
 
184
- const device = await config.store.update(req.params.id, input);
219
+ const device = await store.update(req.params.id, input);
185
220
  if (!device) {
186
221
  return res.status(404).json({ error: 'Device not found' });
187
222
  }
@@ -200,19 +235,19 @@ export function createDevicesPlugin(config: DevicesPluginConfig): Plugin {
200
235
  pluginId: 'devices',
201
236
  handler: async (req: Request, res: Response) => {
202
237
  try {
203
- const device = await config.store.getById(req.params.id);
238
+ const device = await store.getById(req.params.id);
204
239
  if (!device) {
205
240
  return res.status(404).json({ error: 'Device not found' });
206
241
  }
207
242
 
208
- const deleted = await config.store.delete(req.params.id);
243
+ const deleted = await store.delete(req.params.id);
209
244
  if (!deleted) {
210
245
  return res.status(404).json({ error: 'Device not found' });
211
246
  }
212
247
 
213
248
  // Call adapter hook
214
- if (config.adapter.onDeviceDeleted) {
215
- await config.adapter.onDeviceDeleted(device);
249
+ if (adapter.onDeviceDeleted) {
250
+ await adapter.onDeviceDeleted(device);
216
251
  }
217
252
 
218
253
  res.status(204).send();
@@ -230,7 +265,7 @@ export function createDevicesPlugin(config: DevicesPluginConfig): Plugin {
230
265
  pluginId: 'devices',
231
266
  handler: async (req: Request, res: Response) => {
232
267
  try {
233
- const device = await config.store.getById(req.params.id);
268
+ const device = await store.getById(req.params.id);
234
269
  if (!device) {
235
270
  return res.status(404).json({ error: 'Device not found' });
236
271
  }
@@ -295,7 +330,7 @@ export function createDevicesPlugin(config: DevicesPluginConfig): Plugin {
295
330
 
296
331
  async onStop(): Promise<void> {
297
332
  log('Stopping devices plugin');
298
- await config.store.shutdown();
333
+ if (currentStore) { await currentStore.shutdown(); };
299
334
  currentStore = null;
300
335
  currentAdapter = null;
301
336
  currentConfig = null;
@@ -68,8 +68,6 @@ export {
68
68
  } from './token-utils.js';
69
69
  export type { DeviceTokenPair } from './token-utils.js';
70
70
 
71
- // UI Components
72
- export { DevicesStatusWidget } from './DevicesStatusWidget.js';
73
- export type { DevicesStatusWidgetProps } from './DevicesStatusWidget.js';
74
- export { DevicesManagementPage } from './DevicesManagementPage.js';
75
- export type { DevicesManagementPageProps } from './DevicesManagementPage.js';
71
+ // UI Components are exported from main package index (@qwickapps/server)
72
+ // Do NOT export here to avoid loading UI dependencies when importing plugins
73
+
@@ -21,7 +21,9 @@ import type {
21
21
  EntitlementStats,
22
22
  } from './types.js';
23
23
  import type { AuthenticatedRequest } from '../auth/types.js';
24
- import { getCache, type CacheInstance } from '../cache-plugin.js';
24
+ import { getCache, hasCache, type CacheInstance } from '../cache-plugin.js';
25
+ import { hasPostgres, getPostgres } from '../postgres-plugin.js';
26
+ import { postgresEntitlementSource } from './sources/index.js';
25
27
 
26
28
  // Plugin state
27
29
  let primarySource: EntitlementSource | null = null;
@@ -35,67 +37,103 @@ let cacheEnabled = true;
35
37
  let cacheVersion = 1;
36
38
 
37
39
  /**
38
- * Create the Entitlements plugin
40
+ * Create the Entitlements plugin with smart defaults
41
+ *
42
+ * Config is optional - plugin will use defaults and get dependencies from registry.
43
+ * Gracefully handles missing dependencies with clear log messages.
39
44
  */
40
- export function createEntitlementsPlugin(config: EntitlementsPluginConfig): Plugin {
41
- const debug = config.debug || false;
42
- // Routes are mounted under /api by the control panel, so don't include /api in prefix
43
- const apiPrefix = config.api?.prefix || ''; // Framework adds /entitlements prefix automatically (empty string to avoid double slash)
44
- const apiEnabled = config.api?.enabled !== false;
45
- const enableWriteApi = config.api?.enableWrite !== false;
46
-
47
- function log(message: string, data?: Record<string, unknown>) {
48
- if (debug) {
49
- console.log(`[EntitlementsPlugin] ${message}`, data || '');
45
+ export function createEntitlementsPlugin(config: Partial<EntitlementsPluginConfig> = {}): Plugin {
46
+ function log(message: string, data?: Record<string, unknown>, isError = false) {
47
+ const prefix = '[EntitlementsPlugin]';
48
+ if (isError) {
49
+ console.error(`${prefix} ${message}`, data || '');
50
+ } else if (config.debug) {
51
+ console.log(`${prefix} ${message}`, data || '');
50
52
  }
51
53
  }
52
54
 
53
- // Cache key helpers
54
- const keys = {
55
- entitlements: (email: string) => `${cacheKeyPrefix}user:${email.toLowerCase()}`,
56
- mapping: (source: string, id: string) => `${cacheKeyPrefix}mapping:${source}:${id}`,
57
- };
58
-
59
55
  return {
60
56
  id: 'entitlements',
61
57
  name: 'Entitlements',
62
58
  version: '1.0.0',
63
59
 
64
60
  async onStart(_pluginConfig: PluginConfig, registry: PluginRegistry): Promise<void> {
61
+ const logger = registry.getLogger('entitlements');
62
+
63
+ // Check for postgres in registry (needed for default source)
64
+ if (!hasPostgres()) {
65
+ logger.warn('No Database! Entitlements plugin disabled.');
66
+ registry.registerHealthCheck({
67
+ name: 'entitlements-source',
68
+ type: 'custom',
69
+ check: async () => ({
70
+ healthy: false,
71
+ details: {
72
+ error: 'PostgreSQL not available',
73
+ state: 'disabled',
74
+ },
75
+ }),
76
+ });
77
+ return;
78
+ }
79
+
80
+ // Smart defaults - get dependencies from registry
81
+ const source = config.source ?? postgresEntitlementSource({
82
+ pool: () => getPostgres().getPool(),
83
+ autoCreateTables: true,
84
+ });
85
+
86
+ const debug = config.debug ?? false;
87
+ const apiPrefix = config.api?.prefix ?? '/entitlements';
88
+ const apiEnabled = config.api?.enabled ?? true;
89
+ const enableWriteApi = config.api?.enableWrite ?? true;
90
+
65
91
  log('Starting entitlements plugin');
66
92
 
67
93
  // Initialize primary source
68
- await config.source.initialize();
69
- primarySource = config.source;
70
- log('Primary source initialized', { source: config.source.name });
94
+ await source.initialize();
95
+ primarySource = source;
96
+ log('Primary source initialized', { source: source.name });
71
97
 
72
98
  // Initialize additional sources
73
99
  additionalSources = config.additionalSources || [];
74
- for (const source of additionalSources) {
75
- await source.initialize();
76
- log('Additional source initialized', { source: source.name });
100
+ for (const additionalSource of additionalSources) {
101
+ await additionalSource.initialize();
102
+ log('Additional source initialized', { source: additionalSource.name });
77
103
  }
78
104
 
79
105
  // Store config
80
- pluginConfig = config;
106
+ pluginConfig = { ...config, source, debug };
81
107
 
82
- // Setup caching if enabled
108
+ // Setup caching if enabled and available
83
109
  cacheEnabled = config.cache?.enabled !== false;
84
110
  if (cacheEnabled) {
85
- try {
86
- const instanceName = config.cache?.instanceName || 'default';
87
- cacheInstance = getCache(instanceName);
88
- cacheKeyPrefix = config.cache?.keyPrefix || 'entitlements:';
89
- cacheTtl = config.cache?.ttl || 300;
90
- cacheMappingTtl = config.cache?.mappingTtl || cacheTtl * 2;
91
- log('Cache configured', { instanceName, prefix: cacheKeyPrefix, ttl: cacheTtl });
92
- } catch {
93
- log('Cache not available, running without caching');
111
+ if (hasCache()) {
112
+ try {
113
+ const instanceName = config.cache?.instanceName || 'default';
114
+ cacheInstance = getCache(instanceName);
115
+ cacheKeyPrefix = config.cache?.keyPrefix || 'entitlements:';
116
+ cacheTtl = config.cache?.ttl || 300;
117
+ cacheMappingTtl = config.cache?.mappingTtl || cacheTtl * 2;
118
+ log('Cache configured', { instanceName, prefix: cacheKeyPrefix, ttl: cacheTtl });
119
+ } catch {
120
+ log('Cache instance not available, running without caching');
121
+ cacheEnabled = false;
122
+ cacheInstance = null;
123
+ }
124
+ } else {
125
+ log('Cache plugin not available, running without caching');
94
126
  cacheEnabled = false;
95
127
  cacheInstance = null;
96
128
  }
97
129
  }
98
130
 
131
+ // Cache key helpers
132
+ const keys = {
133
+ entitlements: (email: string) => `${cacheKeyPrefix}user:${email.toLowerCase()}`,
134
+ mapping: (sourceId: string, id: string) => `${cacheKeyPrefix}mapping:${sourceId}:${id}`,
135
+ };
136
+
99
137
  // Register health check
100
138
  registry.registerHealthCheck({
101
139
  name: 'entitlements-source',
@@ -104,8 +142,8 @@ export function createEntitlementsPlugin(config: EntitlementsPluginConfig): Plug
104
142
  try {
105
143
  // Use source's isHealthy() method if available (avoids API calls)
106
144
  // Otherwise just check that source is initialized
107
- if (config.source.isHealthy) {
108
- const healthy = await config.source.isHealthy();
145
+ if (source.isHealthy) {
146
+ const healthy = await source.isHealthy();
109
147
  return { healthy };
110
148
  }
111
149
  // Source is healthy if initialized (we got here means it started)
@@ -145,9 +183,9 @@ export function createEntitlementsPlugin(config: EntitlementsPluginConfig): Plug
145
183
  try {
146
184
  const sources = [
147
185
  {
148
- name: config.source.name,
149
- description: config.source.description,
150
- readonly: config.source.readonly ?? false,
186
+ name: source.name,
187
+ description: source.description,
188
+ readonly: source.readonly ?? false,
151
189
  primary: true,
152
190
  },
153
191
  ...additionalSources.map((s) => ({
@@ -159,8 +197,8 @@ export function createEntitlementsPlugin(config: EntitlementsPluginConfig): Plug
159
197
  ];
160
198
 
161
199
  res.json({
162
- readonly: config.source.readonly ?? false,
163
- writeEnabled: enableWriteApi && !config.source.readonly,
200
+ readonly: source.readonly ?? false,
201
+ writeEnabled: enableWriteApi && !source.readonly,
164
202
  cacheEnabled,
165
203
  cacheTtl,
166
204
  sources,
@@ -311,7 +349,7 @@ export function createEntitlementsPlugin(config: EntitlementsPluginConfig): Plug
311
349
  });
312
350
 
313
351
  // Write endpoints (grant/revoke) - only if enabled and source is writable
314
- if (enableWriteApi && !config.source.readonly) {
352
+ if (enableWriteApi && !source.readonly) {
315
353
  // Grant entitlement
316
354
  registry.addRoute({
317
355
  method: 'post',
@@ -50,8 +50,6 @@ export type {
50
50
  EntitlementStats,
51
51
  } from './types.js';
52
52
 
53
- // UI Components
54
- export { EntitlementsStatusWidget } from './EntitlementsStatusWidget.js';
55
- export type { EntitlementsStatusWidgetProps } from './EntitlementsStatusWidget.js';
56
- export { EntitlementsManagementPage } from './EntitlementsManagementPage.js';
57
- export type { EntitlementsManagementPageProps } from './EntitlementsManagementPage.js';
53
+ // UI Components are exported from main package index (@qwickapps/server)
54
+ // Do NOT export here to avoid loading UI dependencies when importing plugins
55
+
@@ -200,10 +200,17 @@ export interface EntitlementsApiConfig {
200
200
 
201
201
  /**
202
202
  * Entitlements plugin configuration
203
+ *
204
+ * All properties are optional - plugin will use smart defaults:
205
+ * - source: Postgres entitlement source using registry's postgres instance
206
+ * - cache: Uses cache from registry if available
207
+ * - api.prefix: '/entitlements'
208
+ * - api.enabled: true
209
+ * - debug: false
203
210
  */
204
211
  export interface EntitlementsPluginConfig {
205
- /** Primary entitlement source */
206
- source: EntitlementSource;
212
+ /** Primary entitlement source (default: postgres source from registry) */
213
+ source?: EntitlementSource;
207
214
 
208
215
  /** Additional sources to query (results are merged) */
209
216
  additionalSources?: EntitlementSource[];
@@ -90,8 +90,6 @@ export type {
90
90
  NotificationsManagerInterface,
91
91
  } from './types.js';
92
92
 
93
- // UI Components
94
- export { NotificationsStatusWidget } from './NotificationsStatusWidget.js';
95
- export { NotificationsManagementPage } from './NotificationsManagementPage.js';
96
- export type { NotificationsStatusWidgetProps } from './NotificationsStatusWidget.js';
97
- export type { NotificationsManagementPageProps } from './NotificationsManagementPage.js';
93
+ // UI Components are exported from main package index (@qwickapps/server)
94
+ // Do NOT export here to avoid loading UI dependencies when importing plugins
95
+
@@ -96,7 +96,10 @@ function validateId(id: string | undefined, paramName: string): { valid: boolean
96
96
  }
97
97
 
98
98
  /**
99
- * Create the Notifications plugin
99
+ * Create the Notifications plugin with smart defaults
100
+ *
101
+ * Config is optional - plugin will use defaults and get dependencies from registry.
102
+ * Gracefully handles missing dependencies with clear log messages.
100
103
  *
101
104
  * @param config Plugin configuration
102
105
  * @returns Plugin instance
@@ -112,11 +115,7 @@ function validateId(id: string | undefined, paramName: string): { valid: boolean
112
115
  * });
113
116
  * ```
114
117
  */
115
- export function createNotificationsPlugin(config: NotificationsPluginConfig): Plugin {
116
- const apiPrefix = config.api?.prefix || '/'; // Framework adds /notifications prefix automatically
117
- const streamEnabled = config.api?.stream !== false;
118
- const statsEnabled = config.api?.stats !== false;
119
-
118
+ export function createNotificationsPlugin(config: Partial<NotificationsPluginConfig> = {}): Plugin {
120
119
  let manager: NotificationsManager | null = null;
121
120
 
122
121
  return {
@@ -129,10 +128,19 @@ export function createNotificationsPlugin(config: NotificationsPluginConfig): Pl
129
128
 
130
129
  // Check for postgres plugin dependency
131
130
  if (!hasPostgres()) {
132
- throw new Error(
133
- 'Notifications plugin requires postgres plugin. ' +
134
- 'Please add createPostgresPlugin() before createNotificationsPlugin().'
135
- );
131
+ logger.warn('No Database! Notifications plugin disabled.');
132
+ registry.registerHealthCheck({
133
+ name: 'notifications',
134
+ type: 'custom',
135
+ check: async () => ({
136
+ healthy: false,
137
+ details: {
138
+ error: 'PostgreSQL not available',
139
+ state: 'disabled',
140
+ },
141
+ }),
142
+ });
143
+ return;
136
144
  }
137
145
 
138
146
  // Get database connection string from postgres plugin
@@ -150,22 +158,43 @@ export function createNotificationsPlugin(config: NotificationsPluginConfig): Pl
150
158
  }
151
159
 
152
160
  if (!connectionString) {
153
- throw new Error(
154
- 'Could not determine PostgreSQL connection string. ' +
155
- 'Ensure DATABASE_URL is set or postgres plugin was configured with a URL.'
156
- );
161
+ logger.warn('Could not determine PostgreSQL connection string. Notifications plugin disabled.');
162
+ registry.registerHealthCheck({
163
+ name: 'notifications',
164
+ type: 'custom',
165
+ check: async () => ({
166
+ healthy: false,
167
+ details: {
168
+ error: 'Connection string not available',
169
+ state: 'disabled',
170
+ },
171
+ }),
172
+ });
173
+ return;
157
174
  }
158
175
 
176
+ // Smart defaults
177
+ const channels = config.channels ?? [];
178
+ const apiPrefix = config.api?.prefix ?? '/'; // Framework adds /notifications prefix automatically
179
+ const streamEnabled = config.api?.stream ?? true;
180
+ const statsEnabled = config.api?.stats ?? true;
181
+
159
182
  logger.debug('Initializing notifications manager', {
160
- channels: config.channels,
183
+ channels: channels,
161
184
  heartbeatInterval: config.heartbeat?.interval,
162
185
  });
163
186
 
187
+ // Create full config with defaults for required fields
188
+ const fullConfig: NotificationsPluginConfig = {
189
+ ...config,
190
+ channels, // Required field with default
191
+ };
192
+
164
193
  // Create and initialize manager
165
194
  manager = new NotificationsManager(
166
195
  connectionString,
167
- config.channels,
168
- config,
196
+ channels,
197
+ fullConfig,
169
198
  logger
170
199
  );
171
200
 
@@ -182,7 +211,7 @@ export function createNotificationsPlugin(config: NotificationsPluginConfig): Pl
182
211
  healthy: health?.isHealthy ?? false,
183
212
  details: {
184
213
  connected: health?.isConnected,
185
- channels: config.channels,
214
+ channels: channels,
186
215
  activeClients: manager?.getStats().currentConnections ?? 0,
187
216
  lastEventAt: health?.lastEventAt?.toISOString(),
188
217
  isReconnecting: health?.isReconnecting,
@@ -273,7 +302,7 @@ export function createNotificationsPlugin(config: NotificationsPluginConfig): Pl
273
302
  const stats = manager.getStats();
274
303
  res.json({
275
304
  ...stats,
276
- channels: config.channels,
305
+ channels: channels,
277
306
  lastEventAt: stats.connectionHealth.lastEventAt?.toISOString(),
278
307
  lastReconnectionAt: stats.lastReconnectionAt?.toISOString(),
279
308
  });
@@ -54,8 +54,6 @@ export { postgresParentalStore } from './stores/index.js';
54
54
  export { kidsAdapter } from './adapters/index.js';
55
55
  export type { KidsAdapterConfig } from './adapters/index.js';
56
56
 
57
- // UI Components
58
- export { ParentalStatusWidget } from './ParentalStatusWidget.js';
59
- export type { ParentalStatusWidgetProps } from './ParentalStatusWidget.js';
60
- export { ParentalManagementPage } from './ParentalManagementPage.js';
61
- export type { ParentalManagementPageProps } from './ParentalManagementPage.js';
57
+ // UI Components are exported from main package index (@qwickapps/server)
58
+ // Do NOT export here to avoid loading UI dependencies when importing plugins
59
+