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
@@ -6,6 +6,7 @@
6
6
 
7
7
  import { asyncHandler } from '../utils/error-handler.js';
8
8
  import * as formatter from '../utils/response-formatter.js';
9
+ import { guardMiddleware } from '../utils/guards.js';
9
10
 
10
11
  /**
11
12
  * Parse custom route definition (e.g., "GET /healthcheck" or "async POST /custom")
@@ -58,11 +59,16 @@ export function createResourceRoutes(resource, version, config = {}, Hono) {
58
59
  const {
59
60
  methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],
60
61
  customMiddleware = [],
61
- enableValidation = true
62
+ enableValidation = true,
63
+ versionPrefix = '', // Empty string by default (calculated in server.js)
64
+ events = null // Event emitter for lifecycle hooks
62
65
  } = config;
63
66
 
64
67
  const resourceName = resource.name;
65
- const basePath = `/${version}/${resourceName}`;
68
+ const basePath = versionPrefix ? `/${versionPrefix}/${resourceName}` : `/${resourceName}`;
69
+
70
+ // Get guards configuration from resource config
71
+ const guards = resource.config?.guards || null;
66
72
 
67
73
  // Apply custom middleware
68
74
  customMiddleware.forEach(middleware => {
@@ -111,7 +117,7 @@ export function createResourceRoutes(resource, version, config = {}, Hono) {
111
117
 
112
118
  // LIST - GET /{version}/{resource}
113
119
  if (methods.includes('GET')) {
114
- app.get('/', asyncHandler(async (c) => {
120
+ app.get('/', guardMiddleware(guards, 'list'), asyncHandler(async (c) => {
115
121
  const query = c.req.query();
116
122
  const limit = parseInt(query.limit) || 100;
117
123
  const offset = parseInt(query.offset) || 0;
@@ -140,22 +146,23 @@ export function createResourceRoutes(resource, version, config = {}, Hono) {
140
146
 
141
147
  // Use query if filters are present
142
148
  if (Object.keys(filters).length > 0) {
143
- items = await resource.query(filters, { limit: limit + offset });
144
- items = items.slice(offset, offset + limit);
149
+ // Query with native offset support (efficient!)
150
+ items = await resource.query(filters, { limit, offset });
151
+ // Note: total is approximate (length of returned items)
152
+ // For exact total count with filters, would need separate count query
145
153
  total = items.length;
146
154
  } else if (partition && partitionValues) {
147
155
  // Query specific partition
148
156
  items = await resource.listPartition({
149
157
  partition,
150
158
  partitionValues,
151
- limit: limit + offset
159
+ limit,
160
+ offset
152
161
  });
153
- items = items.slice(offset, offset + limit);
154
162
  total = items.length;
155
163
  } else {
156
164
  // Regular list
157
- items = await resource.list({ limit: limit + offset });
158
- items = items.slice(offset, offset + limit);
165
+ items = await resource.list({ limit, offset });
159
166
  total = items.length;
160
167
  }
161
168
 
@@ -176,7 +183,7 @@ export function createResourceRoutes(resource, version, config = {}, Hono) {
176
183
 
177
184
  // GET ONE - GET /{version}/{resource}/:id
178
185
  if (methods.includes('GET')) {
179
- app.get('/:id', asyncHandler(async (c) => {
186
+ app.get('/:id', guardMiddleware(guards, 'get'), asyncHandler(async (c) => {
180
187
  const id = c.req.param('id');
181
188
  const query = c.req.query();
182
189
  const partition = query.partition;
@@ -210,12 +217,22 @@ export function createResourceRoutes(resource, version, config = {}, Hono) {
210
217
 
211
218
  // CREATE - POST /{version}/{resource}
212
219
  if (methods.includes('POST')) {
213
- app.post('/', asyncHandler(async (c) => {
220
+ app.post('/', guardMiddleware(guards, 'create'), asyncHandler(async (c) => {
214
221
  const data = await c.req.json();
215
222
 
216
223
  // Validation middleware will run if enabled
217
224
  const item = await resource.insert(data);
218
225
 
226
+ // Emit resource:created event
227
+ if (events) {
228
+ events.emitResourceEvent('created', {
229
+ resource: resourceName,
230
+ id: item.id,
231
+ data: item,
232
+ user: c.get('user')
233
+ });
234
+ }
235
+
219
236
  const location = `${basePath}/${item.id}`;
220
237
  const response = formatter.created(item, location);
221
238
 
@@ -226,7 +243,7 @@ export function createResourceRoutes(resource, version, config = {}, Hono) {
226
243
 
227
244
  // UPDATE (full) - PUT /{version}/{resource}/:id
228
245
  if (methods.includes('PUT')) {
229
- app.put('/:id', asyncHandler(async (c) => {
246
+ app.put('/:id', guardMiddleware(guards, 'update'), asyncHandler(async (c) => {
230
247
  const id = c.req.param('id');
231
248
  const data = await c.req.json();
232
249
 
@@ -240,6 +257,17 @@ export function createResourceRoutes(resource, version, config = {}, Hono) {
240
257
  // Full update
241
258
  const updated = await resource.update(id, data);
242
259
 
260
+ // Emit resource:updated event
261
+ if (events) {
262
+ events.emitResourceEvent('updated', {
263
+ resource: resourceName,
264
+ id: updated.id,
265
+ data: updated,
266
+ previous: existing,
267
+ user: c.get('user')
268
+ });
269
+ }
270
+
243
271
  const response = formatter.success(updated);
244
272
  return c.json(response, response._status);
245
273
  }));
@@ -247,7 +275,7 @@ export function createResourceRoutes(resource, version, config = {}, Hono) {
247
275
 
248
276
  // UPDATE (partial) - PATCH /{version}/{resource}/:id
249
277
  if (methods.includes('PATCH')) {
250
- app.patch('/:id', asyncHandler(async (c) => {
278
+ app.patch('/:id', guardMiddleware(guards, 'update'), asyncHandler(async (c) => {
251
279
  const id = c.req.param('id');
252
280
  const data = await c.req.json();
253
281
 
@@ -262,6 +290,18 @@ export function createResourceRoutes(resource, version, config = {}, Hono) {
262
290
  const merged = { ...existing, ...data, id };
263
291
  const updated = await resource.update(id, merged);
264
292
 
293
+ // Emit resource:updated event
294
+ if (events) {
295
+ events.emitResourceEvent('updated', {
296
+ resource: resourceName,
297
+ id: updated.id,
298
+ data: updated,
299
+ previous: existing,
300
+ partial: true,
301
+ user: c.get('user')
302
+ });
303
+ }
304
+
265
305
  const response = formatter.success(updated);
266
306
  return c.json(response, response._status);
267
307
  }));
@@ -269,7 +309,7 @@ export function createResourceRoutes(resource, version, config = {}, Hono) {
269
309
 
270
310
  // DELETE - DELETE /{version}/{resource}/:id
271
311
  if (methods.includes('DELETE')) {
272
- app.delete('/:id', asyncHandler(async (c) => {
312
+ app.delete('/:id', guardMiddleware(guards, 'delete'), asyncHandler(async (c) => {
273
313
  const id = c.req.param('id');
274
314
 
275
315
  // Check if exists
@@ -281,6 +321,16 @@ export function createResourceRoutes(resource, version, config = {}, Hono) {
281
321
 
282
322
  await resource.delete(id);
283
323
 
324
+ // Emit resource:deleted event
325
+ if (events) {
326
+ events.emitResourceEvent('deleted', {
327
+ resource: resourceName,
328
+ id,
329
+ previous: existing,
330
+ user: c.get('user')
331
+ });
332
+ }
333
+
284
334
  const response = formatter.noContent();
285
335
  return c.json(response, response._status);
286
336
  }));
@@ -291,22 +341,11 @@ export function createResourceRoutes(resource, version, config = {}, Hono) {
291
341
  app.on('HEAD', '/', asyncHandler(async (c) => {
292
342
  // Get statistics
293
343
  const total = await resource.count();
344
+ const version = resource.config?.currentVersion || resource.version || 'v1';
294
345
 
295
- // Get all items to calculate stats (for small datasets)
296
- // For large datasets, this might need optimization
297
- const allItems = await resource.list({ limit: 1000 });
298
-
299
- // Calculate statistics
300
- const stats = {
301
- total,
302
- version: resource.config?.currentVersion || resource.version || 'v1'
303
- };
304
-
305
- // Add resource-specific stats
346
+ // Set resource metadata headers
306
347
  c.header('X-Total-Count', total.toString());
307
- c.header('X-Resource-Version', stats.version);
308
-
309
- // Add schema info
348
+ c.header('X-Resource-Version', version);
310
349
  c.header('X-Schema-Fields', Object.keys(resource.config?.attributes || {}).length.toString());
311
350
 
312
351
  return c.body(null, 200);
@@ -392,8 +431,12 @@ export function createRelationalRoutes(sourceResource, relationName, relationCon
392
431
 
393
432
  // GET /{version}/{resource}/:id/{relation}
394
433
  // Examples: GET /v1/users/user123/posts, GET /v1/users/user123/profile
395
- app.get('/:id', asyncHandler(async (c) => {
396
- const id = c.req.param('id');
434
+ // Note: The :id param comes from parent route mounting (see server.js:469)
435
+ app.get('/', asyncHandler(async (c) => {
436
+ // Get parent route's :id param
437
+ const pathParts = c.req.path.split('/');
438
+ const relationNameIndex = pathParts.lastIndexOf(relationName);
439
+ const id = pathParts[relationNameIndex - 1];
397
440
  const query = c.req.query();
398
441
 
399
442
  // Check if source resource exists