s3db.js 13.4.0 → 13.6.0

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 (110) hide show
  1. package/README.md +25 -10
  2. package/dist/{s3db.cjs.js → s3db.cjs} +38801 -32446
  3. package/dist/s3db.cjs.map +1 -0
  4. package/dist/s3db.es.js +38653 -32291
  5. package/dist/s3db.es.js.map +1 -1
  6. package/package.json +218 -22
  7. package/src/concerns/id.js +90 -6
  8. package/src/concerns/index.js +2 -1
  9. package/src/concerns/password-hashing.js +150 -0
  10. package/src/database.class.js +6 -2
  11. package/src/plugins/api/auth/basic-auth.js +40 -10
  12. package/src/plugins/api/auth/index.js +49 -3
  13. package/src/plugins/api/auth/oauth2-auth.js +171 -0
  14. package/src/plugins/api/auth/oidc-auth.js +789 -0
  15. package/src/plugins/api/auth/oidc-client.js +462 -0
  16. package/src/plugins/api/auth/path-auth-matcher.js +284 -0
  17. package/src/plugins/api/concerns/event-emitter.js +134 -0
  18. package/src/plugins/api/concerns/failban-manager.js +651 -0
  19. package/src/plugins/api/concerns/guards-helpers.js +402 -0
  20. package/src/plugins/api/concerns/metrics-collector.js +346 -0
  21. package/src/plugins/api/index.js +510 -57
  22. package/src/plugins/api/middlewares/failban.js +305 -0
  23. package/src/plugins/api/middlewares/rate-limit.js +301 -0
  24. package/src/plugins/api/middlewares/request-id.js +74 -0
  25. package/src/plugins/api/middlewares/security-headers.js +120 -0
  26. package/src/plugins/api/middlewares/session-tracking.js +194 -0
  27. package/src/plugins/api/routes/auth-routes.js +119 -78
  28. package/src/plugins/api/routes/resource-routes.js +73 -30
  29. package/src/plugins/api/server.js +1139 -45
  30. package/src/plugins/api/utils/custom-routes.js +102 -0
  31. package/src/plugins/api/utils/guards.js +213 -0
  32. package/src/plugins/api/utils/mime-types.js +154 -0
  33. package/src/plugins/api/utils/openapi-generator.js +91 -12
  34. package/src/plugins/api/utils/path-matcher.js +173 -0
  35. package/src/plugins/api/utils/static-filesystem.js +262 -0
  36. package/src/plugins/api/utils/static-s3.js +231 -0
  37. package/src/plugins/api/utils/template-engine.js +188 -0
  38. package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +853 -0
  39. package/src/plugins/cloud-inventory/drivers/aws-driver.js +2554 -0
  40. package/src/plugins/cloud-inventory/drivers/azure-driver.js +637 -0
  41. package/src/plugins/cloud-inventory/drivers/base-driver.js +99 -0
  42. package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +620 -0
  43. package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +698 -0
  44. package/src/plugins/cloud-inventory/drivers/gcp-driver.js +645 -0
  45. package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +559 -0
  46. package/src/plugins/cloud-inventory/drivers/linode-driver.js +614 -0
  47. package/src/plugins/cloud-inventory/drivers/mock-drivers.js +449 -0
  48. package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +771 -0
  49. package/src/plugins/cloud-inventory/drivers/oracle-driver.js +768 -0
  50. package/src/plugins/cloud-inventory/drivers/vultr-driver.js +636 -0
  51. package/src/plugins/cloud-inventory/index.js +20 -0
  52. package/src/plugins/cloud-inventory/registry.js +146 -0
  53. package/src/plugins/cloud-inventory/terraform-exporter.js +362 -0
  54. package/src/plugins/cloud-inventory.plugin.js +1333 -0
  55. package/src/plugins/concerns/plugin-dependencies.js +62 -2
  56. package/src/plugins/eventual-consistency/analytics.js +1 -0
  57. package/src/plugins/eventual-consistency/consolidation.js +2 -2
  58. package/src/plugins/eventual-consistency/garbage-collection.js +2 -2
  59. package/src/plugins/eventual-consistency/install.js +2 -2
  60. package/src/plugins/identity/README.md +335 -0
  61. package/src/plugins/identity/concerns/mfa-manager.js +204 -0
  62. package/src/plugins/identity/concerns/password.js +138 -0
  63. package/src/plugins/identity/concerns/resource-schemas.js +273 -0
  64. package/src/plugins/identity/concerns/token-generator.js +172 -0
  65. package/src/plugins/identity/email-service.js +422 -0
  66. package/src/plugins/identity/index.js +1052 -0
  67. package/src/plugins/identity/oauth2-server.js +1033 -0
  68. package/src/plugins/identity/oidc-discovery.js +285 -0
  69. package/src/plugins/identity/rsa-keys.js +323 -0
  70. package/src/plugins/identity/server.js +500 -0
  71. package/src/plugins/identity/session-manager.js +453 -0
  72. package/src/plugins/identity/ui/layouts/base.js +251 -0
  73. package/src/plugins/identity/ui/middleware.js +135 -0
  74. package/src/plugins/identity/ui/pages/admin/client-form.js +247 -0
  75. package/src/plugins/identity/ui/pages/admin/clients.js +179 -0
  76. package/src/plugins/identity/ui/pages/admin/dashboard.js +181 -0
  77. package/src/plugins/identity/ui/pages/admin/user-form.js +283 -0
  78. package/src/plugins/identity/ui/pages/admin/users.js +263 -0
  79. package/src/plugins/identity/ui/pages/consent.js +262 -0
  80. package/src/plugins/identity/ui/pages/forgot-password.js +104 -0
  81. package/src/plugins/identity/ui/pages/login.js +144 -0
  82. package/src/plugins/identity/ui/pages/mfa-backup-codes.js +180 -0
  83. package/src/plugins/identity/ui/pages/mfa-enrollment.js +187 -0
  84. package/src/plugins/identity/ui/pages/mfa-verification.js +178 -0
  85. package/src/plugins/identity/ui/pages/oauth-error.js +225 -0
  86. package/src/plugins/identity/ui/pages/profile.js +361 -0
  87. package/src/plugins/identity/ui/pages/register.js +226 -0
  88. package/src/plugins/identity/ui/pages/reset-password.js +128 -0
  89. package/src/plugins/identity/ui/pages/verify-email.js +172 -0
  90. package/src/plugins/identity/ui/routes.js +2541 -0
  91. package/src/plugins/identity/ui/styles/main.css +465 -0
  92. package/src/plugins/index.js +4 -1
  93. package/src/plugins/ml/base-model.class.js +65 -16
  94. package/src/plugins/ml/classification-model.class.js +1 -1
  95. package/src/plugins/ml/timeseries-model.class.js +3 -1
  96. package/src/plugins/ml.plugin.js +584 -31
  97. package/src/plugins/shared/error-handler.js +147 -0
  98. package/src/plugins/shared/index.js +9 -0
  99. package/src/plugins/shared/middlewares/compression.js +117 -0
  100. package/src/plugins/shared/middlewares/cors.js +49 -0
  101. package/src/plugins/shared/middlewares/index.js +11 -0
  102. package/src/plugins/shared/middlewares/logging.js +54 -0
  103. package/src/plugins/shared/middlewares/rate-limit.js +73 -0
  104. package/src/plugins/shared/middlewares/security.js +158 -0
  105. package/src/plugins/shared/response-formatter.js +264 -0
  106. package/src/plugins/state-machine.plugin.js +57 -2
  107. package/src/resource.class.js +140 -12
  108. package/src/schema.class.js +30 -1
  109. package/src/validator.class.js +57 -6
  110. package/dist/s3db.cjs.js.map +0 -1
@@ -0,0 +1,346 @@
1
+ /**
2
+ * Metrics Collector
3
+ *
4
+ * Collects and aggregates API metrics for monitoring and observability.
5
+ *
6
+ * Metrics Collected:
7
+ * - Request counts by method, path, status
8
+ * - Request duration percentiles (p50, p95, p99)
9
+ * - Auth success/failure counts
10
+ * - Resource operation counts (created, updated, deleted)
11
+ * - User activity (logins, new users)
12
+ * - Error rates
13
+ *
14
+ * @example
15
+ * const metrics = new MetricsCollector({ enabled: true });
16
+ *
17
+ * // Record request
18
+ * metrics.recordRequest({
19
+ * method: 'GET',
20
+ * path: '/users',
21
+ * status: 200,
22
+ * duration: 45
23
+ * });
24
+ *
25
+ * // Get summary
26
+ * const summary = metrics.getSummary();
27
+ */
28
+
29
+ export class MetricsCollector {
30
+ constructor(options = {}) {
31
+ this.options = {
32
+ enabled: options.enabled !== false, // Enabled by default
33
+ verbose: options.verbose || false,
34
+ maxPathsTracked: options.maxPathsTracked || 100, // Limit memory usage
35
+ resetInterval: options.resetInterval || 300000 // Reset every 5 minutes
36
+ };
37
+
38
+ this.metrics = this._createEmptyMetrics();
39
+ this.startTime = Date.now();
40
+
41
+ // Auto-reset metrics periodically to prevent memory growth
42
+ if (this.options.resetInterval > 0) {
43
+ this.resetTimer = setInterval(() => {
44
+ if (this.options.verbose) {
45
+ console.log('[Metrics] Auto-resetting metrics');
46
+ }
47
+ this.reset();
48
+ }, this.options.resetInterval);
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Create empty metrics structure
54
+ * @private
55
+ */
56
+ _createEmptyMetrics() {
57
+ return {
58
+ requests: {
59
+ total: 0,
60
+ byMethod: {},
61
+ byStatus: {},
62
+ byPath: {},
63
+ durations: []
64
+ },
65
+ auth: {
66
+ success: 0,
67
+ failure: 0,
68
+ byMethod: {}
69
+ },
70
+ resources: {
71
+ created: 0,
72
+ updated: 0,
73
+ deleted: 0,
74
+ byResource: {}
75
+ },
76
+ users: {
77
+ logins: 0,
78
+ newUsers: 0
79
+ },
80
+ errors: {
81
+ total: 0,
82
+ byType: {}
83
+ }
84
+ };
85
+ }
86
+
87
+ /**
88
+ * Record request metrics
89
+ * @param {Object} data - Request data
90
+ */
91
+ recordRequest({ method, path, status, duration }) {
92
+ if (!this.options.enabled) return;
93
+
94
+ const metrics = this.metrics.requests;
95
+
96
+ metrics.total++;
97
+
98
+ // By method
99
+ metrics.byMethod[method] = (metrics.byMethod[method] || 0) + 1;
100
+
101
+ // By status
102
+ const statusGroup = `${Math.floor(status / 100)}xx`;
103
+ metrics.byStatus[statusGroup] = (metrics.byStatus[statusGroup] || 0) + 1;
104
+
105
+ // By path (limit tracking to prevent memory growth)
106
+ if (Object.keys(metrics.byPath).length < this.options.maxPathsTracked || metrics.byPath[path]) {
107
+ if (!metrics.byPath[path]) {
108
+ metrics.byPath[path] = { count: 0, totalDuration: 0, errors: 0 };
109
+ }
110
+ metrics.byPath[path].count++;
111
+ metrics.byPath[path].totalDuration += duration;
112
+ if (status >= 400) {
113
+ metrics.byPath[path].errors++;
114
+ }
115
+ }
116
+
117
+ // Store duration for percentile calculation
118
+ metrics.durations.push(duration);
119
+
120
+ // Keep only last 1000 durations to prevent memory growth
121
+ if (metrics.durations.length > 1000) {
122
+ metrics.durations.shift();
123
+ }
124
+
125
+ if (this.options.verbose) {
126
+ console.log(`[Metrics] Request: ${method} ${path} ${status} (${duration}ms)`);
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Record auth metrics
132
+ * @param {Object} data - Auth data
133
+ */
134
+ recordAuth({ success, method }) {
135
+ if (!this.options.enabled) return;
136
+
137
+ const metrics = this.metrics.auth;
138
+
139
+ if (success) {
140
+ metrics.success++;
141
+ } else {
142
+ metrics.failure++;
143
+ }
144
+
145
+ // By method
146
+ if (!metrics.byMethod[method]) {
147
+ metrics.byMethod[method] = { success: 0, failure: 0 };
148
+ }
149
+
150
+ if (success) {
151
+ metrics.byMethod[method].success++;
152
+ } else {
153
+ metrics.byMethod[method].failure++;
154
+ }
155
+
156
+ if (this.options.verbose) {
157
+ console.log(`[Metrics] Auth: ${method} ${success ? 'success' : 'failure'}`);
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Record resource operation metrics
163
+ * @param {Object} data - Resource operation data
164
+ */
165
+ recordResourceOperation({ action, resource }) {
166
+ if (!this.options.enabled) return;
167
+
168
+ const metrics = this.metrics.resources;
169
+
170
+ // Total by action
171
+ if (action === 'created') metrics.created++;
172
+ else if (action === 'updated') metrics.updated++;
173
+ else if (action === 'deleted') metrics.deleted++;
174
+
175
+ // By resource
176
+ if (!metrics.byResource[resource]) {
177
+ metrics.byResource[resource] = { created: 0, updated: 0, deleted: 0 };
178
+ }
179
+ metrics.byResource[resource][action]++;
180
+
181
+ if (this.options.verbose) {
182
+ console.log(`[Metrics] Resource: ${resource} ${action}`);
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Record user event metrics
188
+ * @param {Object} data - User event data
189
+ */
190
+ recordUserEvent({ action }) {
191
+ if (!this.options.enabled) return;
192
+
193
+ const metrics = this.metrics.users;
194
+
195
+ if (action === 'login') {
196
+ metrics.logins++;
197
+ } else if (action === 'created') {
198
+ metrics.newUsers++;
199
+ }
200
+
201
+ if (this.options.verbose) {
202
+ console.log(`[Metrics] User: ${action}`);
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Record error metrics
208
+ * @param {Object} data - Error data
209
+ */
210
+ recordError({ error, type = 'unknown' }) {
211
+ if (!this.options.enabled) return;
212
+
213
+ const metrics = this.metrics.errors;
214
+
215
+ metrics.total++;
216
+ metrics.byType[type] = (metrics.byType[type] || 0) + 1;
217
+
218
+ if (this.options.verbose) {
219
+ console.log(`[Metrics] Error: ${type} - ${error}`);
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Calculate percentile from sorted array
225
+ * @private
226
+ */
227
+ _percentile(arr, p) {
228
+ if (arr.length === 0) return 0;
229
+ const sorted = [...arr].sort((a, b) => a - b);
230
+ const index = Math.ceil((p / 100) * sorted.length) - 1;
231
+ return sorted[Math.max(0, index)];
232
+ }
233
+
234
+ /**
235
+ * Get metrics summary
236
+ * @returns {Object} Metrics summary
237
+ */
238
+ getSummary() {
239
+ const uptime = Date.now() - this.startTime;
240
+
241
+ return {
242
+ uptime: {
243
+ milliseconds: uptime,
244
+ seconds: Math.floor(uptime / 1000),
245
+ formatted: this._formatDuration(uptime)
246
+ },
247
+ requests: {
248
+ total: this.metrics.requests.total,
249
+ rps: (this.metrics.requests.total / (uptime / 1000)).toFixed(2),
250
+ byMethod: this.metrics.requests.byMethod,
251
+ byStatus: this.metrics.requests.byStatus,
252
+ topPaths: this._getTopPaths(),
253
+ duration: {
254
+ p50: this._percentile(this.metrics.requests.durations, 50),
255
+ p95: this._percentile(this.metrics.requests.durations, 95),
256
+ p99: this._percentile(this.metrics.requests.durations, 99),
257
+ avg: this.metrics.requests.durations.length > 0
258
+ ? (this.metrics.requests.durations.reduce((a, b) => a + b, 0) / this.metrics.requests.durations.length).toFixed(2)
259
+ : 0
260
+ }
261
+ },
262
+ auth: {
263
+ total: this.metrics.auth.success + this.metrics.auth.failure,
264
+ success: this.metrics.auth.success,
265
+ failure: this.metrics.auth.failure,
266
+ successRate: this._calculateRate(this.metrics.auth.success, this.metrics.auth.success + this.metrics.auth.failure),
267
+ byMethod: this.metrics.auth.byMethod
268
+ },
269
+ resources: {
270
+ total: this.metrics.resources.created + this.metrics.resources.updated + this.metrics.resources.deleted,
271
+ created: this.metrics.resources.created,
272
+ updated: this.metrics.resources.updated,
273
+ deleted: this.metrics.resources.deleted,
274
+ byResource: this.metrics.resources.byResource
275
+ },
276
+ users: this.metrics.users,
277
+ errors: {
278
+ total: this.metrics.errors.total,
279
+ rate: this._calculateRate(this.metrics.errors.total, this.metrics.requests.total),
280
+ byType: this.metrics.errors.byType
281
+ }
282
+ };
283
+ }
284
+
285
+ /**
286
+ * Get top paths by request count
287
+ * @private
288
+ */
289
+ _getTopPaths(limit = 10) {
290
+ return Object.entries(this.metrics.requests.byPath)
291
+ .map(([path, data]) => ({
292
+ path,
293
+ count: data.count,
294
+ avgDuration: (data.totalDuration / data.count).toFixed(2),
295
+ errors: data.errors,
296
+ errorRate: this._calculateRate(data.errors, data.count)
297
+ }))
298
+ .sort((a, b) => b.count - a.count)
299
+ .slice(0, limit);
300
+ }
301
+
302
+ /**
303
+ * Calculate rate as percentage
304
+ * @private
305
+ */
306
+ _calculateRate(numerator, denominator) {
307
+ if (denominator === 0) return '0.00%';
308
+ return ((numerator / denominator) * 100).toFixed(2) + '%';
309
+ }
310
+
311
+ /**
312
+ * Format duration in human-readable form
313
+ * @private
314
+ */
315
+ _formatDuration(ms) {
316
+ const seconds = Math.floor(ms / 1000);
317
+ const minutes = Math.floor(seconds / 60);
318
+ const hours = Math.floor(minutes / 60);
319
+ const days = Math.floor(hours / 24);
320
+
321
+ if (days > 0) return `${days}d ${hours % 24}h ${minutes % 60}m`;
322
+ if (hours > 0) return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
323
+ if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
324
+ return `${seconds}s`;
325
+ }
326
+
327
+ /**
328
+ * Reset metrics
329
+ */
330
+ reset() {
331
+ this.metrics = this._createEmptyMetrics();
332
+ this.startTime = Date.now();
333
+ }
334
+
335
+ /**
336
+ * Stop metrics collection and cleanup
337
+ */
338
+ stop() {
339
+ if (this.resetTimer) {
340
+ clearInterval(this.resetTimer);
341
+ this.resetTimer = null;
342
+ }
343
+ }
344
+ }
345
+
346
+ export default MetricsCollector;