s3db.js 13.6.0 → 14.0.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 (193) hide show
  1. package/README.md +139 -43
  2. package/dist/s3db.cjs +72425 -38970
  3. package/dist/s3db.cjs.map +1 -1
  4. package/dist/s3db.es.js +72177 -38764
  5. package/dist/s3db.es.js.map +1 -1
  6. package/mcp/lib/base-handler.js +157 -0
  7. package/mcp/lib/handlers/connection-handler.js +280 -0
  8. package/mcp/lib/handlers/query-handler.js +533 -0
  9. package/mcp/lib/handlers/resource-handler.js +428 -0
  10. package/mcp/lib/tool-registry.js +336 -0
  11. package/mcp/lib/tools/connection-tools.js +161 -0
  12. package/mcp/lib/tools/query-tools.js +267 -0
  13. package/mcp/lib/tools/resource-tools.js +404 -0
  14. package/package.json +94 -49
  15. package/src/clients/memory-client.class.js +346 -191
  16. package/src/clients/memory-storage.class.js +300 -84
  17. package/src/clients/s3-client.class.js +7 -6
  18. package/src/concerns/geo-encoding.js +19 -2
  19. package/src/concerns/ip.js +59 -9
  20. package/src/concerns/money.js +8 -1
  21. package/src/concerns/password-hashing.js +49 -8
  22. package/src/concerns/plugin-storage.js +186 -18
  23. package/src/concerns/storage-drivers/filesystem-driver.js +284 -0
  24. package/src/database.class.js +139 -29
  25. package/src/errors.js +332 -42
  26. package/src/plugins/api/auth/oidc-auth.js +66 -17
  27. package/src/plugins/api/auth/strategies/base-strategy.class.js +74 -0
  28. package/src/plugins/api/auth/strategies/factory.class.js +63 -0
  29. package/src/plugins/api/auth/strategies/global-strategy.class.js +44 -0
  30. package/src/plugins/api/auth/strategies/path-based-strategy.class.js +83 -0
  31. package/src/plugins/api/auth/strategies/path-rules-strategy.class.js +118 -0
  32. package/src/plugins/api/concerns/failban-manager.js +106 -57
  33. package/src/plugins/api/concerns/opengraph-helper.js +116 -0
  34. package/src/plugins/api/concerns/route-context.js +601 -0
  35. package/src/plugins/api/concerns/state-machine.js +288 -0
  36. package/src/plugins/api/index.js +180 -41
  37. package/src/plugins/api/routes/auth-routes.js +198 -30
  38. package/src/plugins/api/routes/resource-routes.js +19 -4
  39. package/src/plugins/api/server/health-manager.class.js +163 -0
  40. package/src/plugins/api/server/middleware-chain.class.js +310 -0
  41. package/src/plugins/api/server/router.class.js +472 -0
  42. package/src/plugins/api/server.js +280 -1303
  43. package/src/plugins/api/utils/custom-routes.js +17 -5
  44. package/src/plugins/api/utils/guards.js +76 -17
  45. package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
  46. package/src/plugins/api/utils/openapi-generator.js +7 -6
  47. package/src/plugins/api/utils/template-engine.js +77 -3
  48. package/src/plugins/audit.plugin.js +30 -8
  49. package/src/plugins/backup.plugin.js +110 -14
  50. package/src/plugins/cache/cache.class.js +22 -5
  51. package/src/plugins/cache/filesystem-cache.class.js +116 -19
  52. package/src/plugins/cache/memory-cache.class.js +211 -57
  53. package/src/plugins/cache/multi-tier-cache.class.js +371 -0
  54. package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
  55. package/src/plugins/cache/redis-cache.class.js +552 -0
  56. package/src/plugins/cache/s3-cache.class.js +17 -8
  57. package/src/plugins/cache.plugin.js +176 -61
  58. package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
  59. package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
  60. package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
  61. package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
  62. package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
  63. package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
  64. package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
  65. package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
  66. package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
  67. package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
  68. package/src/plugins/cloud-inventory/index.js +29 -8
  69. package/src/plugins/cloud-inventory/registry.js +64 -42
  70. package/src/plugins/cloud-inventory.plugin.js +240 -138
  71. package/src/plugins/concerns/plugin-dependencies.js +54 -0
  72. package/src/plugins/concerns/resource-names.js +100 -0
  73. package/src/plugins/consumers/index.js +10 -2
  74. package/src/plugins/consumers/sqs-consumer.js +12 -2
  75. package/src/plugins/cookie-farm-suite.plugin.js +278 -0
  76. package/src/plugins/cookie-farm.errors.js +73 -0
  77. package/src/plugins/cookie-farm.plugin.js +869 -0
  78. package/src/plugins/costs.plugin.js +7 -1
  79. package/src/plugins/eventual-consistency/analytics.js +94 -19
  80. package/src/plugins/eventual-consistency/config.js +15 -7
  81. package/src/plugins/eventual-consistency/consolidation.js +29 -11
  82. package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
  83. package/src/plugins/eventual-consistency/helpers.js +39 -14
  84. package/src/plugins/eventual-consistency/install.js +21 -2
  85. package/src/plugins/eventual-consistency/utils.js +32 -10
  86. package/src/plugins/fulltext.plugin.js +38 -11
  87. package/src/plugins/geo.plugin.js +61 -9
  88. package/src/plugins/identity/concerns/config.js +61 -0
  89. package/src/plugins/identity/concerns/mfa-manager.js +15 -2
  90. package/src/plugins/identity/concerns/rate-limit.js +124 -0
  91. package/src/plugins/identity/concerns/resource-schemas.js +9 -1
  92. package/src/plugins/identity/concerns/token-generator.js +29 -4
  93. package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
  94. package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
  95. package/src/plugins/identity/drivers/index.js +18 -0
  96. package/src/plugins/identity/drivers/password-driver.js +122 -0
  97. package/src/plugins/identity/email-service.js +17 -2
  98. package/src/plugins/identity/index.js +413 -69
  99. package/src/plugins/identity/oauth2-server.js +413 -30
  100. package/src/plugins/identity/oidc-discovery.js +16 -8
  101. package/src/plugins/identity/rsa-keys.js +115 -35
  102. package/src/plugins/identity/server.js +166 -45
  103. package/src/plugins/identity/session-manager.js +53 -7
  104. package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
  105. package/src/plugins/identity/ui/routes.js +363 -255
  106. package/src/plugins/importer/index.js +153 -20
  107. package/src/plugins/index.js +9 -2
  108. package/src/plugins/kubernetes-inventory/index.js +6 -0
  109. package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
  110. package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
  111. package/src/plugins/kubernetes-inventory.plugin.js +980 -0
  112. package/src/plugins/metrics.plugin.js +64 -16
  113. package/src/plugins/ml/base-model.class.js +25 -15
  114. package/src/plugins/ml/regression-model.class.js +1 -1
  115. package/src/plugins/ml.errors.js +57 -25
  116. package/src/plugins/ml.plugin.js +28 -4
  117. package/src/plugins/namespace.js +210 -0
  118. package/src/plugins/plugin.class.js +180 -8
  119. package/src/plugins/puppeteer/console-monitor.js +729 -0
  120. package/src/plugins/puppeteer/cookie-manager.js +492 -0
  121. package/src/plugins/puppeteer/network-monitor.js +816 -0
  122. package/src/plugins/puppeteer/performance-manager.js +746 -0
  123. package/src/plugins/puppeteer/proxy-manager.js +478 -0
  124. package/src/plugins/puppeteer/stealth-manager.js +556 -0
  125. package/src/plugins/puppeteer.errors.js +81 -0
  126. package/src/plugins/puppeteer.plugin.js +1327 -0
  127. package/src/plugins/queue-consumer.plugin.js +69 -14
  128. package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
  129. package/src/plugins/recon/concerns/command-runner.js +148 -0
  130. package/src/plugins/recon/concerns/diff-detector.js +372 -0
  131. package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
  132. package/src/plugins/recon/concerns/process-manager.js +338 -0
  133. package/src/plugins/recon/concerns/report-generator.js +478 -0
  134. package/src/plugins/recon/concerns/security-analyzer.js +571 -0
  135. package/src/plugins/recon/concerns/target-normalizer.js +68 -0
  136. package/src/plugins/recon/config/defaults.js +321 -0
  137. package/src/plugins/recon/config/resources.js +370 -0
  138. package/src/plugins/recon/index.js +778 -0
  139. package/src/plugins/recon/managers/dependency-manager.js +174 -0
  140. package/src/plugins/recon/managers/scheduler-manager.js +179 -0
  141. package/src/plugins/recon/managers/storage-manager.js +745 -0
  142. package/src/plugins/recon/managers/target-manager.js +274 -0
  143. package/src/plugins/recon/stages/asn-stage.js +314 -0
  144. package/src/plugins/recon/stages/certificate-stage.js +84 -0
  145. package/src/plugins/recon/stages/dns-stage.js +107 -0
  146. package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
  147. package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
  148. package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
  149. package/src/plugins/recon/stages/http-stage.js +89 -0
  150. package/src/plugins/recon/stages/latency-stage.js +148 -0
  151. package/src/plugins/recon/stages/massdns-stage.js +302 -0
  152. package/src/plugins/recon/stages/osint-stage.js +1373 -0
  153. package/src/plugins/recon/stages/ports-stage.js +169 -0
  154. package/src/plugins/recon/stages/screenshot-stage.js +94 -0
  155. package/src/plugins/recon/stages/secrets-stage.js +514 -0
  156. package/src/plugins/recon/stages/subdomains-stage.js +295 -0
  157. package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
  158. package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
  159. package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
  160. package/src/plugins/recon/stages/whois-stage.js +349 -0
  161. package/src/plugins/recon.plugin.js +75 -0
  162. package/src/plugins/recon.plugin.js.backup +2635 -0
  163. package/src/plugins/relation.errors.js +87 -14
  164. package/src/plugins/replicator.plugin.js +514 -137
  165. package/src/plugins/replicators/base-replicator.class.js +89 -1
  166. package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
  167. package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
  168. package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
  169. package/src/plugins/replicators/mysql-replicator.class.js +52 -17
  170. package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
  171. package/src/plugins/replicators/postgres-replicator.class.js +62 -27
  172. package/src/plugins/replicators/s3db-replicator.class.js +25 -18
  173. package/src/plugins/replicators/schema-sync.helper.js +3 -3
  174. package/src/plugins/replicators/sqs-replicator.class.js +8 -2
  175. package/src/plugins/replicators/turso-replicator.class.js +23 -3
  176. package/src/plugins/replicators/webhook-replicator.class.js +42 -4
  177. package/src/plugins/s3-queue.plugin.js +464 -65
  178. package/src/plugins/scheduler.plugin.js +20 -6
  179. package/src/plugins/state-machine.plugin.js +40 -9
  180. package/src/plugins/tfstate/README.md +126 -126
  181. package/src/plugins/tfstate/base-driver.js +28 -4
  182. package/src/plugins/tfstate/errors.js +65 -10
  183. package/src/plugins/tfstate/filesystem-driver.js +52 -8
  184. package/src/plugins/tfstate/index.js +163 -90
  185. package/src/plugins/tfstate/s3-driver.js +64 -6
  186. package/src/plugins/ttl.plugin.js +72 -17
  187. package/src/plugins/vector/distances.js +18 -12
  188. package/src/plugins/vector/kmeans.js +26 -4
  189. package/src/resource.class.js +115 -19
  190. package/src/testing/factory.class.js +20 -3
  191. package/src/testing/seeder.class.js +7 -1
  192. package/src/clients/memory-client.md +0 -917
  193. package/src/plugins/cloud-inventory/drivers/mock-drivers.js +0 -449
@@ -0,0 +1,472 @@
1
+ /**
2
+ * Router - Handles all route mounting for API server
3
+ *
4
+ * Separates routing concerns from server lifecycle management.
5
+ * Responsible for mounting:
6
+ * - Resource routes (auto-generated CRUD)
7
+ * - Authentication routes
8
+ * - Static file serving
9
+ * - Custom user routes
10
+ * - Relational routes (if RelationPlugin active)
11
+ * - Admin routes (failban, metrics)
12
+ */
13
+
14
+ import { createResourceRoutes, createRelationalRoutes } from '../routes/resource-routes.js';
15
+ import { createAuthRoutes } from '../routes/auth-routes.js';
16
+ import { mountCustomRoutes } from '../utils/custom-routes.js';
17
+ import * as formatter from '../../shared/response-formatter.js';
18
+ import { createFilesystemHandler, validateFilesystemConfig } from '../utils/static-filesystem.js';
19
+ import { createS3Handler, validateS3Config } from '../utils/static-s3.js';
20
+ import { createFailbanAdminRoutes } from '../middlewares/failban.js';
21
+
22
+ export class Router {
23
+ constructor({ database, resources, routes, versionPrefix, auth, static: staticConfigs, failban, metrics, relationsPlugin, authMiddleware, verbose, Hono }) {
24
+ this.database = database;
25
+ this.resources = resources || {};
26
+ this.routes = routes || {};
27
+ this.versionPrefix = versionPrefix;
28
+ this.auth = auth;
29
+ this.staticConfigs = staticConfigs || [];
30
+ this.failban = failban;
31
+ this.metrics = metrics;
32
+ this.relationsPlugin = relationsPlugin;
33
+ this.authMiddleware = authMiddleware;
34
+ this.verbose = verbose;
35
+ this.Hono = Hono;
36
+ }
37
+
38
+ /**
39
+ * Mount all routes on Hono app
40
+ * @param {Hono} app - Hono application instance
41
+ * @param {Object} events - Event emitter
42
+ */
43
+ mount(app, events) {
44
+ // Static files first (give them priority)
45
+ this.mountStaticRoutes(app);
46
+
47
+ // Resource routes
48
+ this.mountResourceRoutes(app, events);
49
+
50
+ // Authentication routes
51
+ this.mountAuthRoutes(app);
52
+
53
+ // Relational routes (if RelationPlugin is active)
54
+ this.mountRelationalRoutes(app);
55
+
56
+ // Plugin-level custom routes
57
+ this.mountCustomRoutes(app);
58
+
59
+ // Admin routes (failban, metrics)
60
+ this.mountAdminRoutes(app);
61
+ }
62
+
63
+ /**
64
+ * Mount resource routes (auto-generated CRUD)
65
+ * @private
66
+ */
67
+ mountResourceRoutes(app, events) {
68
+ const databaseResources = this.database.resources;
69
+
70
+ for (const [name, resource] of Object.entries(databaseResources)) {
71
+ const resourceConfig = this.resources[name];
72
+ const isPluginResource = name.startsWith('plg_');
73
+
74
+ // Internal plugin resources require explicit opt-in
75
+ if (isPluginResource && !resourceConfig) {
76
+ if (this.verbose) {
77
+ console.log(`[API Router] Skipping internal resource '${name}' (not included in config.resources)`);
78
+ }
79
+ continue;
80
+ }
81
+
82
+ // Allow explicit disabling via config
83
+ if (resourceConfig?.enabled === false) {
84
+ if (this.verbose) {
85
+ console.log(`[API Router] Resource '${name}' disabled via config.resources`);
86
+ }
87
+ continue;
88
+ }
89
+
90
+ // Determine version
91
+ const version = resource.config?.currentVersion || resource.version || 'v1';
92
+
93
+ // Determine version prefix (resource-level overrides global)
94
+ let versionPrefixConfig;
95
+ if (resourceConfig && resourceConfig.versionPrefix !== undefined) {
96
+ versionPrefixConfig = resourceConfig.versionPrefix;
97
+ } else if (resource.config && resource.config.versionPrefix !== undefined) {
98
+ versionPrefixConfig = resource.config.versionPrefix;
99
+ } else if (this.versionPrefix !== undefined) {
100
+ versionPrefixConfig = this.versionPrefix;
101
+ } else {
102
+ versionPrefixConfig = false;
103
+ }
104
+
105
+ // Calculate the actual prefix to use
106
+ let prefix = '';
107
+ if (versionPrefixConfig === true) {
108
+ prefix = version;
109
+ } else if (versionPrefixConfig === false) {
110
+ prefix = '';
111
+ } else if (typeof versionPrefixConfig === 'string') {
112
+ prefix = versionPrefixConfig;
113
+ }
114
+
115
+ // Prepare custom middleware
116
+ const middlewares = [];
117
+
118
+ // Add global authentication middleware unless explicitly disabled
119
+ const authDisabled = resourceConfig?.auth === false;
120
+
121
+ if (this.authMiddleware && !authDisabled) {
122
+ middlewares.push(this.authMiddleware);
123
+ }
124
+
125
+ // Add resource-specific middleware from config
126
+ const extraMiddleware = resourceConfig?.customMiddleware;
127
+ if (extraMiddleware) {
128
+ const toRegister = Array.isArray(extraMiddleware) ? extraMiddleware : [extraMiddleware];
129
+
130
+ for (const middleware of toRegister) {
131
+ if (typeof middleware === 'function') {
132
+ middlewares.push(middleware);
133
+ } else if (this.verbose) {
134
+ console.warn(`[API Router] Ignoring non-function middleware for resource '${name}'`);
135
+ }
136
+ }
137
+ }
138
+
139
+ // Normalize HTTP methods
140
+ let methods = resourceConfig?.methods || resource.config?.methods;
141
+ if (!Array.isArray(methods) || methods.length === 0) {
142
+ methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];
143
+ } else {
144
+ methods = methods
145
+ .filter(Boolean)
146
+ .map(method => typeof method === 'string' ? method.toUpperCase() : method);
147
+ }
148
+
149
+ // Determine validation toggle
150
+ const enableValidation = resourceConfig?.validation !== undefined
151
+ ? resourceConfig.validation !== false
152
+ : resource.config?.validation !== false;
153
+
154
+ // Create resource routes
155
+ const resourceApp = createResourceRoutes(resource, version, {
156
+ methods,
157
+ customMiddleware: middlewares,
158
+ enableValidation,
159
+ versionPrefix: prefix,
160
+ events
161
+ }, this.Hono);
162
+
163
+ // Mount resource routes
164
+ const mountPath = prefix ? `/${prefix}/${name}` : `/${name}`;
165
+ app.route(mountPath, resourceApp);
166
+
167
+ if (this.verbose) {
168
+ console.log(`[API Router] Mounted routes for resource '${name}' at ${mountPath}`);
169
+ }
170
+
171
+ // Mount custom routes for this resource
172
+ if (resource.config?.routes) {
173
+ const routeContext = {
174
+ resource,
175
+ database: this.database,
176
+ resourceName: name,
177
+ version
178
+ };
179
+
180
+ mountCustomRoutes(resourceApp, resource.config.routes, routeContext, this.verbose);
181
+ }
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Mount authentication routes
187
+ * @private
188
+ */
189
+ mountAuthRoutes(app) {
190
+ const { drivers, resource: resourceName, usernameField, passwordField, registration, loginThrottle } = this.auth;
191
+
192
+ const identityPlugin = this.database?.plugins?.identity || this.database?.plugins?.Identity;
193
+ if (identityPlugin) {
194
+ if (this.verbose) {
195
+ console.warn('[API Router] IdentityPlugin detected. Skipping built-in auth routes.');
196
+ }
197
+ return;
198
+ }
199
+
200
+ // Find first JWT driver
201
+ const jwtDriver = drivers?.find(d => d.driver === 'jwt');
202
+
203
+ if (!jwtDriver) {
204
+ return;
205
+ }
206
+
207
+ // Get auth resource
208
+ const authResource = this.database.resources[resourceName];
209
+ if (!authResource) {
210
+ console.error(`[API Router] Auth resource '${resourceName}' not found. Skipping auth routes.`);
211
+ return;
212
+ }
213
+
214
+ const driverConfig = jwtDriver.config || {};
215
+ const registrationConfig = {
216
+ enabled: driverConfig.allowRegistration === true ||
217
+ driverConfig.registration?.enabled === true ||
218
+ registration?.enabled === true,
219
+ allowedFields: Array.isArray(driverConfig.registration?.allowedFields)
220
+ ? driverConfig.registration.allowedFields
221
+ : Array.isArray(registration?.allowedFields)
222
+ ? registration.allowedFields
223
+ : [],
224
+ defaultRole: driverConfig.registration?.defaultRole ??
225
+ registration?.defaultRole ??
226
+ 'user'
227
+ };
228
+
229
+ const driverLoginThrottle = driverConfig.loginThrottle || {};
230
+ const loginThrottleConfig = {
231
+ enabled: driverLoginThrottle.enabled ?? loginThrottle?.enabled ?? true,
232
+ maxAttempts: driverLoginThrottle.maxAttempts || loginThrottle?.maxAttempts || 5,
233
+ windowMs: driverLoginThrottle.windowMs || loginThrottle?.windowMs || 60_000,
234
+ blockDurationMs: driverLoginThrottle.blockDurationMs || loginThrottle?.blockDurationMs || 300_000,
235
+ maxEntries: driverLoginThrottle.maxEntries || loginThrottle?.maxEntries || 10_000
236
+ };
237
+
238
+ // Prepare auth config for routes
239
+ const authConfig = {
240
+ driver: 'jwt',
241
+ usernameField,
242
+ passwordField,
243
+ jwtSecret: driverConfig.jwtSecret || driverConfig.secret,
244
+ jwtExpiresIn: driverConfig.jwtExpiresIn || driverConfig.expiresIn || '7d',
245
+ passphrase: driverConfig.passphrase || 'secret',
246
+ allowRegistration: registrationConfig.enabled,
247
+ registration: registrationConfig,
248
+ loginThrottle: loginThrottleConfig
249
+ };
250
+
251
+ // Create auth routes
252
+ const authApp = createAuthRoutes(authResource, authConfig);
253
+
254
+ // Mount auth routes at /auth
255
+ app.route('/auth', authApp);
256
+
257
+ if (this.verbose) {
258
+ console.log('[API Router] Mounted auth routes (driver: jwt) at /auth');
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Mount static file serving routes
264
+ * @private
265
+ */
266
+ mountStaticRoutes(app) {
267
+ if (!this.staticConfigs || this.staticConfigs.length === 0) {
268
+ return;
269
+ }
270
+
271
+ if (!Array.isArray(this.staticConfigs)) {
272
+ throw new Error('Static config must be an array of mount points');
273
+ }
274
+
275
+ for (const [index, config] of this.staticConfigs.entries()) {
276
+ try {
277
+ // Validate required fields
278
+ if (!config.driver) {
279
+ throw new Error(`static[${index}]: "driver" is required (filesystem or s3)`);
280
+ }
281
+
282
+ if (!config.path) {
283
+ throw new Error(`static[${index}]: "path" is required (mount path)`);
284
+ }
285
+
286
+ if (!config.path.startsWith('/')) {
287
+ throw new Error(`static[${index}]: "path" must start with / (got: ${config.path})`);
288
+ }
289
+
290
+ const driverConfig = config.config || {};
291
+
292
+ // Create handler based on driver
293
+ let handler;
294
+
295
+ if (config.driver === 'filesystem') {
296
+ validateFilesystemConfig({ ...config, ...driverConfig });
297
+
298
+ handler = createFilesystemHandler({
299
+ root: config.root,
300
+ index: driverConfig.index,
301
+ fallback: driverConfig.fallback,
302
+ maxAge: driverConfig.maxAge,
303
+ dotfiles: driverConfig.dotfiles,
304
+ etag: driverConfig.etag,
305
+ cors: driverConfig.cors
306
+ });
307
+
308
+ } else if (config.driver === 's3') {
309
+ validateS3Config({ ...config, ...driverConfig });
310
+
311
+ const s3Client = this.database?.client?.client;
312
+
313
+ if (!s3Client) {
314
+ throw new Error(`static[${index}]: S3 driver requires database with S3 client`);
315
+ }
316
+
317
+ handler = createS3Handler({
318
+ s3Client,
319
+ bucket: config.bucket,
320
+ prefix: config.prefix,
321
+ streaming: driverConfig.streaming,
322
+ signedUrlExpiry: driverConfig.signedUrlExpiry,
323
+ maxAge: driverConfig.maxAge,
324
+ cacheControl: driverConfig.cacheControl,
325
+ contentDisposition: driverConfig.contentDisposition,
326
+ etag: driverConfig.etag,
327
+ cors: driverConfig.cors
328
+ });
329
+
330
+ } else {
331
+ throw new Error(
332
+ `static[${index}]: invalid driver "${config.driver}". Valid drivers: filesystem, s3`
333
+ );
334
+ }
335
+
336
+ // Mount handler
337
+ const mountPath = config.path === '/' ? '/*' : `${config.path}/*`;
338
+ app.get(mountPath, handler);
339
+ app.head(mountPath, handler);
340
+
341
+ if (this.verbose) {
342
+ console.log(
343
+ `[API Router] Mounted static files (${config.driver}) at ${config.path}` +
344
+ (config.driver === 'filesystem' ? ` -> ${config.root}` : ` -> s3://${config.bucket}/${config.prefix || ''}`)
345
+ );
346
+ }
347
+
348
+ } catch (err) {
349
+ console.error(`[API Router] Failed to setup static files for index ${index}:`, err.message);
350
+ throw err;
351
+ }
352
+ }
353
+ }
354
+
355
+ /**
356
+ * Mount relational routes (if RelationPlugin is active)
357
+ * @private
358
+ */
359
+ mountRelationalRoutes(app) {
360
+ if (!this.relationsPlugin || !this.relationsPlugin.relations) {
361
+ return;
362
+ }
363
+
364
+ const relations = this.relationsPlugin.relations;
365
+
366
+ if (this.verbose) {
367
+ console.log('[API Router] Setting up relational routes...');
368
+ }
369
+
370
+ for (const [resourceName, relationsDef] of Object.entries(relations)) {
371
+ const resource = this.database.resources[resourceName];
372
+ if (!resource) {
373
+ if (this.verbose) {
374
+ console.warn(`[API Router] Resource '${resourceName}' not found for relational routes`);
375
+ }
376
+ continue;
377
+ }
378
+
379
+ // Skip plugin resources unless explicitly included
380
+ if (resourceName.startsWith('plg_') && !this.resources[resourceName]) {
381
+ continue;
382
+ }
383
+
384
+ const version = resource.config?.currentVersion || resource.version || 'v1';
385
+
386
+ for (const [relationName, relationConfig] of Object.entries(relationsDef)) {
387
+ // Skip belongsTo relations
388
+ if (relationConfig.type === 'belongsTo') {
389
+ continue;
390
+ }
391
+
392
+ // Check if relation should be exposed
393
+ const resourceConfig = this.resources[resourceName];
394
+ const exposeRelation = resourceConfig?.relations?.[relationName]?.expose !== false;
395
+
396
+ if (!exposeRelation) {
397
+ continue;
398
+ }
399
+
400
+ // Create relational routes
401
+ const relationalApp = createRelationalRoutes(
402
+ resource,
403
+ relationName,
404
+ relationConfig,
405
+ version,
406
+ this.Hono
407
+ );
408
+
409
+ // Mount relational routes
410
+ app.route(`/${version}/${resourceName}/:id/${relationName}`, relationalApp);
411
+
412
+ if (this.verbose) {
413
+ console.log(
414
+ `[API Router] Mounted relational route: /${version}/${resourceName}/:id/${relationName} ` +
415
+ `(${relationConfig.type} -> ${relationConfig.resource})`
416
+ );
417
+ }
418
+ }
419
+ }
420
+ }
421
+
422
+ /**
423
+ * Mount plugin-level custom routes
424
+ * @private
425
+ */
426
+ mountCustomRoutes(app) {
427
+ if (!this.routes || Object.keys(this.routes).length === 0) {
428
+ return;
429
+ }
430
+
431
+ const context = {
432
+ database: this.database,
433
+ plugins: this.database?.plugins || {}
434
+ };
435
+
436
+ mountCustomRoutes(app, this.routes, context, this.verbose);
437
+
438
+ if (this.verbose) {
439
+ console.log(`[API Router] Mounted ${Object.keys(this.routes).length} plugin-level custom routes`);
440
+ }
441
+ }
442
+
443
+ /**
444
+ * Mount admin routes (failban, metrics)
445
+ * @private
446
+ */
447
+ mountAdminRoutes(app) {
448
+ // Metrics endpoint
449
+ const metricsEnabled = this.metrics?.options?.enabled ?? false;
450
+ if (metricsEnabled) {
451
+ app.get('/metrics', (c) => {
452
+ const summary = this.metrics.getSummary();
453
+ const response = formatter.success(summary);
454
+ return c.json(response);
455
+ });
456
+
457
+ if (this.verbose) {
458
+ console.log('[API Router] Metrics endpoint enabled at /metrics');
459
+ }
460
+ }
461
+
462
+ // Failban admin endpoints
463
+ if (this.failban) {
464
+ const failbanAdminRoutes = createFailbanAdminRoutes(this.Hono, this.failban);
465
+ app.route('/admin/security', failbanAdminRoutes);
466
+
467
+ if (this.verbose) {
468
+ console.log('[API Router] Failban admin endpoints enabled at /admin/security');
469
+ }
470
+ }
471
+ }
472
+ }