s3db.js 11.3.2 → 12.0.1

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 (83) hide show
  1. package/README.md +102 -8
  2. package/dist/s3db.cjs.js +36945 -15510
  3. package/dist/s3db.cjs.js.map +1 -1
  4. package/dist/s3db.d.ts +66 -1
  5. package/dist/s3db.es.js +36914 -15534
  6. package/dist/s3db.es.js.map +1 -1
  7. package/mcp/entrypoint.js +58 -0
  8. package/mcp/tools/documentation.js +434 -0
  9. package/mcp/tools/index.js +4 -0
  10. package/package.json +35 -15
  11. package/src/behaviors/user-managed.js +13 -6
  12. package/src/client.class.js +79 -49
  13. package/src/concerns/base62.js +85 -0
  14. package/src/concerns/dictionary-encoding.js +294 -0
  15. package/src/concerns/geo-encoding.js +256 -0
  16. package/src/concerns/high-performance-inserter.js +34 -30
  17. package/src/concerns/ip.js +325 -0
  18. package/src/concerns/metadata-encoding.js +345 -66
  19. package/src/concerns/money.js +193 -0
  20. package/src/concerns/partition-queue.js +7 -4
  21. package/src/concerns/plugin-storage.js +97 -47
  22. package/src/database.class.js +76 -74
  23. package/src/errors.js +0 -4
  24. package/src/plugins/api/auth/api-key-auth.js +88 -0
  25. package/src/plugins/api/auth/basic-auth.js +154 -0
  26. package/src/plugins/api/auth/index.js +112 -0
  27. package/src/plugins/api/auth/jwt-auth.js +169 -0
  28. package/src/plugins/api/index.js +544 -0
  29. package/src/plugins/api/middlewares/index.js +15 -0
  30. package/src/plugins/api/middlewares/validator.js +185 -0
  31. package/src/plugins/api/routes/auth-routes.js +241 -0
  32. package/src/plugins/api/routes/resource-routes.js +304 -0
  33. package/src/plugins/api/server.js +354 -0
  34. package/src/plugins/api/utils/error-handler.js +147 -0
  35. package/src/plugins/api/utils/openapi-generator.js +1240 -0
  36. package/src/plugins/api/utils/response-formatter.js +218 -0
  37. package/src/plugins/backup/streaming-exporter.js +132 -0
  38. package/src/plugins/backup.plugin.js +103 -50
  39. package/src/plugins/cache/s3-cache.class.js +95 -47
  40. package/src/plugins/cache.plugin.js +107 -9
  41. package/src/plugins/concerns/plugin-dependencies.js +313 -0
  42. package/src/plugins/concerns/prometheus-formatter.js +255 -0
  43. package/src/plugins/consumers/rabbitmq-consumer.js +4 -0
  44. package/src/plugins/consumers/sqs-consumer.js +4 -0
  45. package/src/plugins/costs.plugin.js +255 -39
  46. package/src/plugins/eventual-consistency/helpers.js +15 -1
  47. package/src/plugins/geo.plugin.js +873 -0
  48. package/src/plugins/importer/index.js +1020 -0
  49. package/src/plugins/index.js +11 -0
  50. package/src/plugins/metrics.plugin.js +163 -4
  51. package/src/plugins/queue-consumer.plugin.js +6 -27
  52. package/src/plugins/relation.errors.js +139 -0
  53. package/src/plugins/relation.plugin.js +1242 -0
  54. package/src/plugins/replicator.plugin.js +2 -1
  55. package/src/plugins/replicators/bigquery-replicator.class.js +180 -8
  56. package/src/plugins/replicators/dynamodb-replicator.class.js +383 -0
  57. package/src/plugins/replicators/index.js +28 -3
  58. package/src/plugins/replicators/mongodb-replicator.class.js +391 -0
  59. package/src/plugins/replicators/mysql-replicator.class.js +558 -0
  60. package/src/plugins/replicators/planetscale-replicator.class.js +409 -0
  61. package/src/plugins/replicators/postgres-replicator.class.js +182 -7
  62. package/src/plugins/replicators/s3db-replicator.class.js +1 -12
  63. package/src/plugins/replicators/schema-sync.helper.js +601 -0
  64. package/src/plugins/replicators/sqs-replicator.class.js +11 -9
  65. package/src/plugins/replicators/turso-replicator.class.js +416 -0
  66. package/src/plugins/replicators/webhook-replicator.class.js +612 -0
  67. package/src/plugins/state-machine.plugin.js +122 -68
  68. package/src/plugins/tfstate/README.md +745 -0
  69. package/src/plugins/tfstate/base-driver.js +80 -0
  70. package/src/plugins/tfstate/errors.js +112 -0
  71. package/src/plugins/tfstate/filesystem-driver.js +129 -0
  72. package/src/plugins/tfstate/index.js +2660 -0
  73. package/src/plugins/tfstate/s3-driver.js +192 -0
  74. package/src/plugins/ttl.plugin.js +536 -0
  75. package/src/resource.class.js +315 -36
  76. package/src/s3db.d.ts +66 -1
  77. package/src/schema.class.js +366 -32
  78. package/SECURITY.md +0 -76
  79. package/src/partition-drivers/base-partition-driver.js +0 -106
  80. package/src/partition-drivers/index.js +0 -66
  81. package/src/partition-drivers/memory-partition-driver.js +0 -289
  82. package/src/partition-drivers/sqs-partition-driver.js +0 -337
  83. package/src/partition-drivers/sync-partition-driver.js +0 -38
@@ -0,0 +1,544 @@
1
+ /**
2
+ * API Plugin - RESTful HTTP API for s3db.js resources
3
+ *
4
+ * Transforms s3db.js resources into HTTP REST endpoints with:
5
+ * - Multiple authentication methods (JWT, API Key, Basic Auth, Public)
6
+ * - Automatic versioning based on resource version
7
+ * - Production features (CORS, Rate Limiting, Logging, Compression)
8
+ * - Schema validation middleware
9
+ * - Custom middleware support
10
+ *
11
+ * @example
12
+ * const apiPlugin = new ApiPlugin({
13
+ * port: 3000,
14
+ * docs: { enabled: true },
15
+ * auth: {
16
+ * jwt: { enabled: true, secret: 'my-secret' },
17
+ * apiKey: { enabled: true }
18
+ * },
19
+ * resources: {
20
+ * cars: {
21
+ * auth: ['jwt', 'apiKey'],
22
+ * methods: ['GET', 'POST', 'PUT', 'DELETE']
23
+ * }
24
+ * },
25
+ * cors: { enabled: true },
26
+ * rateLimit: { enabled: true, maxRequests: 100 },
27
+ * logging: { enabled: true },
28
+ * compression: { enabled: true },
29
+ * validation: { enabled: true }
30
+ * });
31
+ *
32
+ * await database.usePlugin(apiPlugin);
33
+ */
34
+
35
+ import { Plugin } from '../plugin.class.js';
36
+ import { ApiServer } from './server.js';
37
+ import { requirePluginDependency } from '../concerns/plugin-dependencies.js';
38
+ import tryFn from '../../concerns/try-fn.js';
39
+
40
+ /**
41
+ * API Plugin class
42
+ * @class
43
+ * @extends Plugin
44
+ */
45
+ export class ApiPlugin extends Plugin {
46
+ /**
47
+ * Create API Plugin instance
48
+ * @param {Object} options - Plugin configuration
49
+ */
50
+ constructor(options = {}) {
51
+ super(options);
52
+
53
+ this.config = {
54
+ // Server configuration
55
+ port: options.port || 3000,
56
+ host: options.host || '0.0.0.0',
57
+ verbose: options.verbose || false,
58
+
59
+ // API Documentation (supports both new and legacy formats)
60
+ docs: {
61
+ enabled: options.docs?.enabled !== false && options.docsEnabled !== false, // Enable by default
62
+ ui: options.docs?.ui || 'redoc', // 'swagger' or 'redoc' (redoc is prettier!)
63
+ title: options.docs?.title || options.apiTitle || 's3db.js API',
64
+ version: options.docs?.version || options.apiVersion || '1.0.0',
65
+ description: options.docs?.description || options.apiDescription || 'Auto-generated REST API for s3db.js resources'
66
+ },
67
+
68
+ // Authentication configuration
69
+ auth: {
70
+ jwt: {
71
+ enabled: options.auth?.jwt?.enabled || false,
72
+ secret: options.auth?.jwt?.secret || null,
73
+ expiresIn: options.auth?.jwt?.expiresIn || '7d'
74
+ },
75
+ apiKey: {
76
+ enabled: options.auth?.apiKey?.enabled || false,
77
+ headerName: options.auth?.apiKey?.headerName || 'X-API-Key'
78
+ },
79
+ basic: {
80
+ enabled: options.auth?.basic?.enabled || false,
81
+ realm: options.auth?.basic?.realm || 'API Access'
82
+ }
83
+ },
84
+
85
+ // Resource configuration
86
+ resources: options.resources || {},
87
+
88
+ // CORS configuration
89
+ cors: {
90
+ enabled: options.cors?.enabled || false,
91
+ origin: options.cors?.origin || '*',
92
+ methods: options.cors?.methods || ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
93
+ allowedHeaders: options.cors?.allowedHeaders || ['Content-Type', 'Authorization', 'X-API-Key'],
94
+ exposedHeaders: options.cors?.exposedHeaders || ['X-Total-Count', 'X-Page-Count'],
95
+ credentials: options.cors?.credentials !== false,
96
+ maxAge: options.cors?.maxAge || 86400
97
+ },
98
+
99
+ // Rate limiting configuration
100
+ rateLimit: {
101
+ enabled: options.rateLimit?.enabled || false,
102
+ windowMs: options.rateLimit?.windowMs || 60000, // 1 minute
103
+ maxRequests: options.rateLimit?.maxRequests || 100,
104
+ keyGenerator: options.rateLimit?.keyGenerator || null
105
+ },
106
+
107
+ // Logging configuration
108
+ logging: {
109
+ enabled: options.logging?.enabled || false,
110
+ format: options.logging?.format || ':method :path :status :response-time ms',
111
+ verbose: options.logging?.verbose || false
112
+ },
113
+
114
+ // Compression configuration
115
+ compression: {
116
+ enabled: options.compression?.enabled || false,
117
+ threshold: options.compression?.threshold || 1024, // 1KB
118
+ level: options.compression?.level || 6
119
+ },
120
+
121
+ // Validation configuration
122
+ validation: {
123
+ enabled: options.validation?.enabled !== false,
124
+ validateOnInsert: options.validation?.validateOnInsert !== false,
125
+ validateOnUpdate: options.validation?.validateOnUpdate !== false,
126
+ returnValidationErrors: options.validation?.returnValidationErrors !== false
127
+ },
128
+
129
+ // Content Security Policy (CSP) configuration
130
+ csp: {
131
+ enabled: options.csp?.enabled || false,
132
+ // Default CSP that works with Redoc v2.5.1 (allows CDN scripts/styles)
133
+ directives: options.csp?.directives || {
134
+ 'default-src': ["'self'"],
135
+ 'script-src': ["'self'", "'unsafe-inline'", 'https://cdn.redoc.ly/redoc/v2.5.1/'],
136
+ 'style-src': ["'self'", "'unsafe-inline'", 'https://cdn.redoc.ly/redoc/v2.5.1/', 'https://fonts.googleapis.com'],
137
+ 'font-src': ["'self'", 'https://fonts.gstatic.com'],
138
+ 'img-src': ["'self'", 'data:', 'https:'],
139
+ 'connect-src': ["'self'"]
140
+ },
141
+ reportOnly: options.csp?.reportOnly || false, // If true, uses Content-Security-Policy-Report-Only
142
+ reportUri: options.csp?.reportUri || null
143
+ },
144
+
145
+ // Custom global middlewares
146
+ middlewares: options.middlewares || []
147
+ };
148
+
149
+ this.server = null;
150
+ this.usersResource = null;
151
+ }
152
+
153
+ /**
154
+ * Validate plugin dependencies
155
+ * @private
156
+ */
157
+ async _validateDependencies() {
158
+ await requirePluginDependency('api-plugin', {
159
+ throwOnError: true,
160
+ checkVersions: true
161
+ });
162
+ }
163
+
164
+ /**
165
+ * Install plugin
166
+ */
167
+ async onInstall() {
168
+ if (this.config.verbose) {
169
+ console.log('[API Plugin] Installing...');
170
+ }
171
+
172
+ // Validate dependencies
173
+ try {
174
+ await this._validateDependencies();
175
+ } catch (err) {
176
+ console.error('[API Plugin] Dependency validation failed:', err.message);
177
+ throw err;
178
+ }
179
+
180
+ // Create users resource if authentication is enabled
181
+ const authEnabled = this.config.auth.jwt.enabled ||
182
+ this.config.auth.apiKey.enabled ||
183
+ this.config.auth.basic.enabled;
184
+
185
+ if (authEnabled) {
186
+ await this._createUsersResource();
187
+ }
188
+
189
+ // Setup middlewares
190
+ await this._setupMiddlewares();
191
+
192
+ if (this.config.verbose) {
193
+ console.log('[API Plugin] Installed successfully');
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Create users resource for authentication
199
+ * @private
200
+ */
201
+ async _createUsersResource() {
202
+ const [ok, err, resource] = await tryFn(() =>
203
+ this.database.createResource({
204
+ name: 'plg_users',
205
+ attributes: {
206
+ id: 'string|required',
207
+ username: 'string|required|minlength:3',
208
+ email: 'string|optional|email',
209
+ password: 'secret|required|minlength:8',
210
+ apiKey: 'string|optional',
211
+ jwtSecret: 'string|optional',
212
+ role: 'string|default:user',
213
+ active: 'boolean|default:true',
214
+ createdAt: 'string|optional',
215
+ lastLoginAt: 'string|optional',
216
+ metadata: 'json|optional'
217
+ },
218
+ behavior: 'body-overflow',
219
+ timestamps: true,
220
+ createdBy: 'ApiPlugin'
221
+ })
222
+ );
223
+
224
+ if (ok) {
225
+ this.usersResource = resource;
226
+ if (this.config.verbose) {
227
+ console.log('[API Plugin] Created plg_users resource for authentication');
228
+ }
229
+ } else if (this.database.resources.plg_users) {
230
+ // Resource already exists
231
+ this.usersResource = this.database.resources.plg_users;
232
+ if (this.config.verbose) {
233
+ console.log('[API Plugin] Using existing plg_users resource');
234
+ }
235
+ } else {
236
+ throw err;
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Setup middlewares
242
+ * @private
243
+ */
244
+ async _setupMiddlewares() {
245
+ const middlewares = [];
246
+
247
+ // Add request ID middleware
248
+ middlewares.push(async (c, next) => {
249
+ c.set('requestId', crypto.randomUUID());
250
+ c.set('verbose', this.config.verbose);
251
+ await next();
252
+ });
253
+
254
+ // Add CORS middleware
255
+ if (this.config.cors.enabled) {
256
+ const corsMiddleware = await this._createCorsMiddleware();
257
+ middlewares.push(corsMiddleware);
258
+ }
259
+
260
+ // Add CSP middleware
261
+ if (this.config.csp.enabled) {
262
+ const cspMiddleware = await this._createCSPMiddleware();
263
+ middlewares.push(cspMiddleware);
264
+ }
265
+
266
+ // Add rate limiting middleware
267
+ if (this.config.rateLimit.enabled) {
268
+ const rateLimitMiddleware = await this._createRateLimitMiddleware();
269
+ middlewares.push(rateLimitMiddleware);
270
+ }
271
+
272
+ // Add logging middleware
273
+ if (this.config.logging.enabled) {
274
+ const loggingMiddleware = await this._createLoggingMiddleware();
275
+ middlewares.push(loggingMiddleware);
276
+ }
277
+
278
+ // Add compression middleware
279
+ if (this.config.compression.enabled) {
280
+ const compressionMiddleware = await this._createCompressionMiddleware();
281
+ middlewares.push(compressionMiddleware);
282
+ }
283
+
284
+ // Add custom middlewares
285
+ middlewares.push(...this.config.middlewares);
286
+
287
+ // Store compiled middlewares
288
+ this.compiledMiddlewares = middlewares;
289
+ }
290
+
291
+ /**
292
+ * Create CORS middleware (placeholder)
293
+ * @private
294
+ */
295
+ async _createCorsMiddleware() {
296
+ return async (c, next) => {
297
+ const { origin, methods, allowedHeaders, exposedHeaders, credentials, maxAge } = this.config.cors;
298
+
299
+ // Set CORS headers
300
+ c.header('Access-Control-Allow-Origin', origin);
301
+ c.header('Access-Control-Allow-Methods', methods.join(', '));
302
+ c.header('Access-Control-Allow-Headers', allowedHeaders.join(', '));
303
+ c.header('Access-Control-Expose-Headers', exposedHeaders.join(', '));
304
+
305
+ if (credentials) {
306
+ c.header('Access-Control-Allow-Credentials', 'true');
307
+ }
308
+
309
+ c.header('Access-Control-Max-Age', maxAge.toString());
310
+
311
+ // Handle OPTIONS preflight
312
+ if (c.req.method === 'OPTIONS') {
313
+ return c.body(null, 204);
314
+ }
315
+
316
+ await next();
317
+ };
318
+ }
319
+
320
+ /**
321
+ * Create CSP middleware
322
+ * @private
323
+ */
324
+ async _createCSPMiddleware() {
325
+ return async (c, next) => {
326
+ const { directives, reportOnly, reportUri } = this.config.csp;
327
+
328
+ // Build CSP header value from directives
329
+ const cspParts = [];
330
+ for (const [directive, values] of Object.entries(directives)) {
331
+ if (Array.isArray(values) && values.length > 0) {
332
+ cspParts.push(`${directive} ${values.join(' ')}`);
333
+ } else if (typeof values === 'string') {
334
+ cspParts.push(`${directive} ${values}`);
335
+ }
336
+ }
337
+
338
+ // Add report-uri if specified
339
+ if (reportUri) {
340
+ cspParts.push(`report-uri ${reportUri}`);
341
+ }
342
+
343
+ const cspValue = cspParts.join('; ');
344
+
345
+ // Set appropriate header (report-only or enforced)
346
+ const headerName = reportOnly
347
+ ? 'Content-Security-Policy-Report-Only'
348
+ : 'Content-Security-Policy';
349
+
350
+ c.header(headerName, cspValue);
351
+
352
+ await next();
353
+ };
354
+ }
355
+
356
+ /**
357
+ * Create rate limiting middleware (placeholder)
358
+ * @private
359
+ */
360
+ async _createRateLimitMiddleware() {
361
+ const requests = new Map();
362
+ const { windowMs, maxRequests, keyGenerator } = this.config.rateLimit;
363
+
364
+ return async (c, next) => {
365
+ // Generate key (IP or custom)
366
+ const key = keyGenerator
367
+ ? keyGenerator(c)
368
+ : c.req.header('x-forwarded-for') || c.req.header('cf-connecting-ip') || 'unknown';
369
+
370
+ // Get or create request count
371
+ if (!requests.has(key)) {
372
+ requests.set(key, { count: 0, resetAt: Date.now() + windowMs });
373
+ }
374
+
375
+ const record = requests.get(key);
376
+
377
+ // Reset if window expired
378
+ if (Date.now() > record.resetAt) {
379
+ record.count = 0;
380
+ record.resetAt = Date.now() + windowMs;
381
+ }
382
+
383
+ // Check limit
384
+ if (record.count >= maxRequests) {
385
+ const retryAfter = Math.ceil((record.resetAt - Date.now()) / 1000);
386
+ c.header('Retry-After', retryAfter.toString());
387
+ c.header('X-RateLimit-Limit', maxRequests.toString());
388
+ c.header('X-RateLimit-Remaining', '0');
389
+ c.header('X-RateLimit-Reset', record.resetAt.toString());
390
+
391
+ return c.json({
392
+ success: false,
393
+ error: {
394
+ message: 'Rate limit exceeded',
395
+ code: 'RATE_LIMIT_EXCEEDED',
396
+ details: { retryAfter }
397
+ }
398
+ }, 429);
399
+ }
400
+
401
+ // Increment count
402
+ record.count++;
403
+
404
+ // Set rate limit headers
405
+ c.header('X-RateLimit-Limit', maxRequests.toString());
406
+ c.header('X-RateLimit-Remaining', (maxRequests - record.count).toString());
407
+ c.header('X-RateLimit-Reset', record.resetAt.toString());
408
+
409
+ await next();
410
+ };
411
+ }
412
+
413
+ /**
414
+ * Create logging middleware (placeholder)
415
+ * @private
416
+ */
417
+ async _createLoggingMiddleware() {
418
+ return async (c, next) => {
419
+ const start = Date.now();
420
+ const method = c.req.method;
421
+ const path = c.req.path;
422
+ const requestId = c.get('requestId');
423
+
424
+ await next();
425
+
426
+ const duration = Date.now() - start;
427
+ const status = c.res.status;
428
+ const user = c.get('user')?.username || 'anonymous';
429
+
430
+ console.log(`[API Plugin] ${requestId} - ${method} ${path} ${status} ${duration}ms - ${user}`);
431
+ };
432
+ }
433
+
434
+ /**
435
+ * Create compression middleware (placeholder)
436
+ * @private
437
+ */
438
+ async _createCompressionMiddleware() {
439
+ return async (c, next) => {
440
+ await next();
441
+
442
+ // Note: Actual compression would require proper streaming support
443
+ // For now, this is a placeholder
444
+ const acceptEncoding = c.req.header('accept-encoding') || '';
445
+
446
+ if (acceptEncoding.includes('gzip')) {
447
+ c.header('Content-Encoding', 'gzip');
448
+ } else if (acceptEncoding.includes('deflate')) {
449
+ c.header('Content-Encoding', 'deflate');
450
+ }
451
+ };
452
+ }
453
+
454
+ /**
455
+ * Start plugin
456
+ */
457
+ async onStart() {
458
+ if (this.config.verbose) {
459
+ console.log('[API Plugin] Starting server...');
460
+ }
461
+
462
+ // Create server instance
463
+ this.server = new ApiServer({
464
+ port: this.config.port,
465
+ host: this.config.host,
466
+ database: this.database,
467
+ resources: this.config.resources,
468
+ middlewares: this.compiledMiddlewares,
469
+ verbose: this.config.verbose,
470
+ auth: this.config.auth,
471
+ docsEnabled: this.config.docs.enabled,
472
+ docsUI: this.config.docs.ui,
473
+ apiTitle: this.config.docs.title,
474
+ apiVersion: this.config.docs.version,
475
+ apiDescription: this.config.docs.description
476
+ });
477
+
478
+ // Start server
479
+ await this.server.start();
480
+
481
+ this.emit('plugin.started', {
482
+ port: this.config.port,
483
+ host: this.config.host
484
+ });
485
+ }
486
+
487
+ /**
488
+ * Stop plugin
489
+ */
490
+ async onStop() {
491
+ if (this.config.verbose) {
492
+ console.log('[API Plugin] Stopping server...');
493
+ }
494
+
495
+ if (this.server) {
496
+ await this.server.stop();
497
+ this.server = null;
498
+ }
499
+
500
+ this.emit('plugin.stopped');
501
+ }
502
+
503
+ /**
504
+ * Uninstall plugin
505
+ */
506
+ async onUninstall(options = {}) {
507
+ const { purgeData = false } = options;
508
+
509
+ // Stop server if running
510
+ await this.onStop();
511
+
512
+ // Optionally delete users resource
513
+ if (purgeData && this.usersResource) {
514
+ // Delete all users (plugin data cleanup happens automatically via base Plugin class)
515
+ const [ok] = await tryFn(() => this.database.deleteResource('plg_users'));
516
+
517
+ if (ok && this.config.verbose) {
518
+ console.log('[API Plugin] Deleted plg_users resource');
519
+ }
520
+ }
521
+
522
+ if (this.config.verbose) {
523
+ console.log('[API Plugin] Uninstalled successfully');
524
+ }
525
+ }
526
+
527
+ /**
528
+ * Get server information
529
+ * @returns {Object} Server info
530
+ */
531
+ getServerInfo() {
532
+ return this.server ? this.server.getInfo() : { isRunning: false };
533
+ }
534
+
535
+ /**
536
+ * Get Hono app instance (for advanced usage)
537
+ * @returns {Hono|null} Hono app
538
+ */
539
+ getApp() {
540
+ return this.server ? this.server.getApp() : null;
541
+ }
542
+ }
543
+
544
+ export default ApiPlugin;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Middlewares - Export all API middlewares
3
+ */
4
+
5
+ export { createValidationMiddleware, createQueryValidation, listQueryValidation } from './validator.js';
6
+
7
+ // Note: CORS, Rate Limiting, Logging, and Compression middlewares
8
+ // are currently implemented in api.plugin.js as inline functions.
9
+ // They can be extracted to separate files if needed for better organization.
10
+
11
+ export default {
12
+ createValidationMiddleware,
13
+ createQueryValidation,
14
+ listQueryValidation
15
+ };