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,601 @@
1
+ /**
2
+ * Route Context - Enhanced context wrapper for custom routes
3
+ *
4
+ * Provides clean, dev-friendly access to database, resources, and utilities
5
+ * without verbose c.get('customRouteContext').database.resources.xxx
6
+ *
7
+ * @example
8
+ * // Automatic (default behavior)
9
+ * routes: {
10
+ * '/:id': async (c, ctx) => {
11
+ * const { db, resources, resource, validator } = ctx;
12
+ * const url = await resources.urls.get(c.req.param('id'));
13
+ * return c.json({ url });
14
+ * }
15
+ * }
16
+ */
17
+
18
+ /**
19
+ * RouteContext class - provides clean access to database, resources, and helpers
20
+ */
21
+ export class RouteContext {
22
+ /**
23
+ * Create RouteContext
24
+ * @param {Object} honoContext - Hono context (c)
25
+ * @param {Object} database - s3db.js Database instance
26
+ * @param {Object} resource - Current resource (for resource-level routes)
27
+ * @param {Object} plugins - Plugin instances
28
+ */
29
+ constructor(honoContext, database, resource = null, plugins = {}) {
30
+ this.c = honoContext;
31
+ this.db = database;
32
+ this.database = database; // Alias
33
+ this._currentResource = resource;
34
+ this.plugins = plugins;
35
+
36
+ // Create resources proxy for clean access
37
+ this.resources = this._createResourcesProxy();
38
+
39
+ // Validator helper
40
+ this.validator = this._createValidator();
41
+
42
+ // Current resource shortcut (for resource-level routes)
43
+ this.resource = resource;
44
+ }
45
+
46
+ /**
47
+ * Create Proxy for easy resource access
48
+ * @private
49
+ */
50
+ _createResourcesProxy() {
51
+ return new Proxy({}, {
52
+ get: (target, prop) => {
53
+ // Check if resource exists
54
+ if (this.database.resources[prop]) {
55
+ return this.database.resources[prop];
56
+ }
57
+
58
+ // Helpful error message
59
+ const available = Object.keys(this.database.resources);
60
+ throw new Error(
61
+ `Resource "${prop}" not found. Available resources: ${available.join(', ')}`
62
+ );
63
+ },
64
+
65
+ // List available resources (for debugging)
66
+ ownKeys: () => {
67
+ return Object.keys(this.database.resources);
68
+ },
69
+
70
+ // Make resources enumerable
71
+ getOwnPropertyDescriptor: (target, prop) => {
72
+ if (this.database.resources[prop]) {
73
+ return {
74
+ enumerable: true,
75
+ configurable: true
76
+ };
77
+ }
78
+ return undefined;
79
+ }
80
+ });
81
+ }
82
+
83
+ /**
84
+ * Create validator helper
85
+ * @private
86
+ */
87
+ _createValidator() {
88
+ const ctx = this;
89
+
90
+ return {
91
+ /**
92
+ * Validate data against resource schema
93
+ * @param {string|Object} resourceOrData - Resource name or data object
94
+ * @param {Object} data - Data to validate (if first param is resource name)
95
+ * @returns {Object} { valid: boolean, errors?: Array }
96
+ */
97
+ validate(resourceOrData, data = null) {
98
+ let resource;
99
+ let dataToValidate;
100
+
101
+ // Case 1: validate(data) - use current resource (resource-level routes)
102
+ if (typeof resourceOrData === 'object' && data === null) {
103
+ if (!ctx._currentResource) {
104
+ throw new Error('validator.validate(data) requires a current resource. Use validator.validate("resourceName", data) instead.');
105
+ }
106
+ resource = ctx._currentResource;
107
+ dataToValidate = resourceOrData;
108
+ }
109
+ // Case 2: validate("resourceName", data)
110
+ else if (typeof resourceOrData === 'string' && data !== null) {
111
+ resource = ctx.resources[resourceOrData]; // Will throw if not found (via Proxy)
112
+ dataToValidate = data;
113
+ }
114
+ else {
115
+ throw new Error('Invalid arguments. Use validator.validate(data) or validator.validate("resourceName", data)');
116
+ }
117
+
118
+ // Run validation via resource schema
119
+ const validation = resource.schema.validate(dataToValidate);
120
+
121
+ if (validation === true) {
122
+ return { valid: true };
123
+ } else {
124
+ return {
125
+ valid: false,
126
+ errors: Array.isArray(validation) ? validation : [validation]
127
+ };
128
+ }
129
+ },
130
+
131
+ /**
132
+ * Validate and throw if invalid
133
+ * @param {string|Object} resourceOrData - Resource name or data object
134
+ * @param {Object} data - Data to validate
135
+ * @throws {Error} Validation error with details
136
+ */
137
+ validateOrThrow(resourceOrData, data = null) {
138
+ const result = this.validate(resourceOrData, data);
139
+
140
+ if (!result.valid) {
141
+ const error = new Error('Validation failed');
142
+ error.code = 'VALIDATION_ERROR';
143
+ error.errors = result.errors;
144
+ error.status = 400;
145
+ throw error;
146
+ }
147
+ },
148
+
149
+ /**
150
+ * Validate request body against resource schema
151
+ * @param {string} resourceName - Resource name (optional if current resource exists)
152
+ * @returns {Promise<Object>} { valid: boolean, data?: Object, errors?: Array }
153
+ */
154
+ async validateBody(resourceName = null) {
155
+ const body = await ctx.c.req.json();
156
+
157
+ if (resourceName) {
158
+ const result = this.validate(resourceName, body);
159
+ return { ...result, data: body };
160
+ } else {
161
+ const result = this.validate(body);
162
+ return { ...result, data: body };
163
+ }
164
+ }
165
+ };
166
+ }
167
+
168
+ // ============================================
169
+ // Request Helpers (proxy to Hono context)
170
+ // ============================================
171
+
172
+ /**
173
+ * Get path parameter
174
+ * @param {string} name - Parameter name
175
+ * @returns {string} Parameter value
176
+ */
177
+ param(name) {
178
+ return this.c.req.param(name);
179
+ }
180
+
181
+ /**
182
+ * Get all path parameters
183
+ * @returns {Object} All parameters
184
+ */
185
+ params() {
186
+ return this.c.req.param();
187
+ }
188
+
189
+ /**
190
+ * Get query parameter
191
+ * @param {string} name - Query parameter name
192
+ * @returns {string|undefined} Query value
193
+ */
194
+ query(name) {
195
+ return this.c.req.query(name);
196
+ }
197
+
198
+ /**
199
+ * Get all query parameters
200
+ * @returns {Object} All query parameters
201
+ */
202
+ queries() {
203
+ return this.c.req.query();
204
+ }
205
+
206
+ /**
207
+ * Get request header
208
+ * @param {string} name - Header name
209
+ * @returns {string|undefined} Header value
210
+ */
211
+ header(name) {
212
+ return this.c.req.header(name);
213
+ }
214
+
215
+ /**
216
+ * Parse JSON body
217
+ * @returns {Promise<Object>} Parsed body
218
+ */
219
+ async body() {
220
+ return await this.c.req.json();
221
+ }
222
+
223
+ /**
224
+ * Get request body as text
225
+ * @returns {Promise<string>} Body text
226
+ */
227
+ async text() {
228
+ return await this.c.req.text();
229
+ }
230
+
231
+ /**
232
+ * Get request body as FormData
233
+ * @returns {Promise<FormData>} FormData
234
+ */
235
+ async formData() {
236
+ return await this.c.req.formData();
237
+ }
238
+
239
+ // ============================================
240
+ // Response Helpers (shortcuts)
241
+ // ============================================
242
+
243
+ /**
244
+ * Send JSON response
245
+ * @param {Object} data - Response data
246
+ * @param {number} status - HTTP status code
247
+ * @returns {Response} Hono response
248
+ */
249
+ json(data, status = 200) {
250
+ return this.c.json(data, status);
251
+ }
252
+
253
+ /**
254
+ * Send success response
255
+ * @param {Object} data - Response data
256
+ * @param {number} status - HTTP status code
257
+ * @returns {Response} Success response
258
+ */
259
+ success(data, status = 200) {
260
+ return this.c.json({
261
+ success: true,
262
+ data
263
+ }, status);
264
+ }
265
+
266
+ /**
267
+ * Send error response
268
+ * @param {string} message - Error message
269
+ * @param {number} status - HTTP status code
270
+ * @returns {Response} Error response
271
+ */
272
+ error(message, status = 400) {
273
+ return this.c.json({
274
+ success: false,
275
+ error: {
276
+ message,
277
+ code: 'ERROR',
278
+ status
279
+ }
280
+ }, status);
281
+ }
282
+
283
+ /**
284
+ * Send 404 Not Found
285
+ * @param {string} message - Optional message
286
+ * @returns {Response} 404 response
287
+ */
288
+ notFound(message = 'Not found') {
289
+ return this.c.json({
290
+ success: false,
291
+ error: {
292
+ message,
293
+ code: 'NOT_FOUND',
294
+ status: 404
295
+ }
296
+ }, 404);
297
+ }
298
+
299
+ /**
300
+ * Send 401 Unauthorized
301
+ * @param {string} message - Optional message
302
+ * @returns {Response} 401 response
303
+ */
304
+ unauthorized(message = 'Unauthorized') {
305
+ return this.c.json({
306
+ success: false,
307
+ error: {
308
+ message,
309
+ code: 'UNAUTHORIZED',
310
+ status: 401
311
+ }
312
+ }, 401);
313
+ }
314
+
315
+ /**
316
+ * Send 403 Forbidden
317
+ * @param {string} message - Optional message
318
+ * @returns {Response} 403 response
319
+ */
320
+ forbidden(message = 'Forbidden') {
321
+ return this.c.json({
322
+ success: false,
323
+ error: {
324
+ message,
325
+ code: 'FORBIDDEN',
326
+ status: 403
327
+ }
328
+ }, 403);
329
+ }
330
+
331
+ /**
332
+ * Send HTML response
333
+ * @param {string} html - HTML content
334
+ * @param {number} status - HTTP status code
335
+ * @returns {Response} HTML response
336
+ */
337
+ html(html, status = 200) {
338
+ return this.c.html(html, status);
339
+ }
340
+
341
+ /**
342
+ * Redirect to URL
343
+ * @param {string} url - Target URL
344
+ * @param {number} status - HTTP status code (default 302)
345
+ * @returns {Response} Redirect response
346
+ */
347
+ redirect(url, status = 302) {
348
+ return this.c.redirect(url, status);
349
+ }
350
+
351
+ /**
352
+ * Render template (if template engine is configured)
353
+ * @param {string|JSX.Element} template - Template name or JSX element
354
+ * @param {Object} data - Data to pass to template
355
+ * @param {Object} options - Render options
356
+ * @returns {Promise<Response>} Rendered HTML response
357
+ */
358
+ async render(template, data = {}, options = {}) {
359
+ if (!this.c.render) {
360
+ throw new Error(
361
+ 'Template engine not configured. Use ApiPlugin with templates: { engine: "ejs" | "pug" | "jsx" }'
362
+ );
363
+ }
364
+
365
+ return await this.c.render(template, data, options);
366
+ }
367
+
368
+ // ============================================
369
+ // Context Helpers
370
+ // ============================================
371
+
372
+ /**
373
+ * Get authenticated user (if auth is enabled)
374
+ * @returns {Object|null} User object
375
+ */
376
+ get user() {
377
+ return this.c.get('user') || null;
378
+ }
379
+
380
+ /**
381
+ * Get session (if session tracking enabled)
382
+ * @returns {Object|null} Session object
383
+ */
384
+ get session() {
385
+ return this.c.get('session') || null;
386
+ }
387
+
388
+ /**
389
+ * Get session ID (if session tracking enabled)
390
+ * @returns {string|null} Session ID
391
+ */
392
+ get sessionId() {
393
+ return this.c.get('sessionId') || null;
394
+ }
395
+
396
+ /**
397
+ * Get request ID (if request ID tracking enabled)
398
+ * @returns {string|null} Request ID
399
+ */
400
+ get requestId() {
401
+ return this.c.get('requestId') || null;
402
+ }
403
+
404
+ /**
405
+ * Check if user is authenticated
406
+ * @returns {boolean} True if authenticated
407
+ */
408
+ get isAuthenticated() {
409
+ return !!this.user;
410
+ }
411
+
412
+ /**
413
+ * Check if user has scope
414
+ * @param {string} scope - Scope to check
415
+ * @returns {boolean} True if user has scope
416
+ */
417
+ hasScope(scope) {
418
+ return this.user?.scopes?.includes(scope) || false;
419
+ }
420
+
421
+ /**
422
+ * Check if user has any of the scopes
423
+ * @param {Array<string>} scopes - Scopes to check
424
+ * @returns {boolean} True if user has any scope
425
+ */
426
+ hasAnyScope(...scopes) {
427
+ return scopes.some(scope => this.hasScope(scope));
428
+ }
429
+
430
+ /**
431
+ * Check if user has all scopes
432
+ * @param {Array<string>} scopes - Scopes to check
433
+ * @returns {boolean} True if user has all scopes
434
+ */
435
+ hasAllScopes(...scopes) {
436
+ return scopes.every(scope => this.hasScope(scope));
437
+ }
438
+
439
+ /**
440
+ * Require authentication (throw if not authenticated)
441
+ * @throws {Error} If not authenticated
442
+ */
443
+ requireAuth() {
444
+ if (!this.isAuthenticated) {
445
+ throw Object.assign(
446
+ new Error('Authentication required'),
447
+ { status: 401, code: 'UNAUTHORIZED' }
448
+ );
449
+ }
450
+ }
451
+
452
+ /**
453
+ * Require scope (throw if not authorized)
454
+ * @param {string} scope - Required scope
455
+ * @throws {Error} If scope missing
456
+ */
457
+ requireScope(scope) {
458
+ this.requireAuth();
459
+
460
+ if (!this.hasScope(scope)) {
461
+ throw Object.assign(
462
+ new Error(`Scope required: ${scope}`),
463
+ { status: 403, code: 'FORBIDDEN' }
464
+ );
465
+ }
466
+ }
467
+
468
+ // ============================================
469
+ // Partition Helpers (for Guards)
470
+ // ============================================
471
+
472
+ /**
473
+ * Set partition filter for current query (used by guards for tenant isolation)
474
+ * @param {string} partitionName - Partition name (e.g., 'byUserId')
475
+ * @param {Object} partitionFields - Partition field values (e.g., { userId: 'user123' })
476
+ * @returns {void}
477
+ *
478
+ * @example
479
+ * // In guard:
480
+ * users.guard = {
481
+ * list: (ctx) => {
482
+ * if (ctx.user.scopes?.includes('preset:admin')) {
483
+ * return true; // Admin sees everything
484
+ * }
485
+ *
486
+ * // Regular user sees only their data (O(1) via partition)
487
+ * ctx.setPartition('byUserId', { userId: ctx.user.id });
488
+ * return true;
489
+ * }
490
+ * };
491
+ */
492
+ setPartition(partitionName, partitionFields) {
493
+ if (!this._partitionFilters) {
494
+ this._partitionFilters = [];
495
+ }
496
+
497
+ this._partitionFilters.push({ partitionName, partitionFields });
498
+ }
499
+
500
+ /**
501
+ * Get partition filters set by guards
502
+ * @returns {Array} Partition filters
503
+ * @internal
504
+ */
505
+ getPartitionFilters() {
506
+ return this._partitionFilters || [];
507
+ }
508
+
509
+ /**
510
+ * Clear partition filters
511
+ * @returns {void}
512
+ * @internal
513
+ */
514
+ clearPartitionFilters() {
515
+ this._partitionFilters = [];
516
+ }
517
+
518
+ /**
519
+ * Check if partition filters are set
520
+ * @returns {boolean} True if partition filters exist
521
+ */
522
+ hasPartitionFilters() {
523
+ return this._partitionFilters && this._partitionFilters.length > 0;
524
+ }
525
+ }
526
+
527
+ /**
528
+ * Wrap route handler to provide enhanced context
529
+ *
530
+ * @param {Function} handler - Route handler (c, ctx) => Response
531
+ * @param {Object} options - Options
532
+ * @param {Object} options.resource - Current resource (for resource-level routes)
533
+ * @returns {Function} Wrapped handler
534
+ *
535
+ * @example
536
+ * // Plugin-level route
537
+ * routes: {
538
+ * 'GET /health': withContext(async (c, ctx) => {
539
+ * const { db, resources } = ctx;
540
+ * return ctx.success({ uptime: process.uptime() });
541
+ * })
542
+ * }
543
+ *
544
+ * @example
545
+ * // Resource-level route (ctx.resource is auto-populated)
546
+ * resources: {
547
+ * users: {
548
+ * api: {
549
+ * 'POST /users/:id/reset-password': withContext(async (c, ctx) => {
550
+ * const { resource, validator } = ctx;
551
+ * const id = ctx.param('id');
552
+ *
553
+ * // Validate body against current resource schema
554
+ * const { valid, errors } = await ctx.validator.validateBody();
555
+ * if (!valid) return ctx.error(errors, 400);
556
+ *
557
+ * // Use current resource
558
+ * const user = await resource.get(id);
559
+ * return ctx.success({ user });
560
+ * })
561
+ * }
562
+ * }
563
+ * }
564
+ */
565
+ export function withContext(handler, options = {}) {
566
+ return async (c) => {
567
+ // Extract legacy context (for backward compatibility)
568
+ const legacyContext = c.get('customRouteContext') || {};
569
+ const { database, resource, plugins = {} } = legacyContext;
570
+
571
+ // Use options.resource if provided (resource-level routes)
572
+ const currentResource = options.resource || resource || null;
573
+
574
+ // Create enhanced context
575
+ const ctx = new RouteContext(c, database, currentResource, plugins);
576
+
577
+ // Call handler with both Hono context and enhanced context
578
+ return await handler(c, ctx);
579
+ };
580
+ }
581
+
582
+ /**
583
+ * Auto-wrap handler - automatically wraps handlers to provide enhanced context
584
+ * This is used internally by the API Plugin to make enhanced context the default
585
+ *
586
+ * @param {Function} handler - Route handler
587
+ * @param {Object} options - Options
588
+ * @returns {Function} Wrapped handler
589
+ * @private
590
+ */
591
+ export function autoWrapHandler(handler, options = {}) {
592
+ // Check if handler is already wrapped or if it only expects 1 argument (c)
593
+ // In that case, don't wrap (backward compatibility)
594
+ if (handler.length === 1) {
595
+ // Handler only expects (c) - use legacy behavior
596
+ return handler;
597
+ }
598
+
599
+ // Handler expects (c, ctx) - wrap it
600
+ return withContext(handler, options);
601
+ }