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.
- package/README.md +139 -43
- package/dist/s3db.cjs +72425 -38970
- package/dist/s3db.cjs.map +1 -1
- package/dist/s3db.es.js +72177 -38764
- package/dist/s3db.es.js.map +1 -1
- package/mcp/lib/base-handler.js +157 -0
- package/mcp/lib/handlers/connection-handler.js +280 -0
- package/mcp/lib/handlers/query-handler.js +533 -0
- package/mcp/lib/handlers/resource-handler.js +428 -0
- package/mcp/lib/tool-registry.js +336 -0
- package/mcp/lib/tools/connection-tools.js +161 -0
- package/mcp/lib/tools/query-tools.js +267 -0
- package/mcp/lib/tools/resource-tools.js +404 -0
- package/package.json +94 -49
- package/src/clients/memory-client.class.js +346 -191
- package/src/clients/memory-storage.class.js +300 -84
- package/src/clients/s3-client.class.js +7 -6
- package/src/concerns/geo-encoding.js +19 -2
- package/src/concerns/ip.js +59 -9
- package/src/concerns/money.js +8 -1
- package/src/concerns/password-hashing.js +49 -8
- package/src/concerns/plugin-storage.js +186 -18
- package/src/concerns/storage-drivers/filesystem-driver.js +284 -0
- package/src/database.class.js +139 -29
- package/src/errors.js +332 -42
- package/src/plugins/api/auth/oidc-auth.js +66 -17
- package/src/plugins/api/auth/strategies/base-strategy.class.js +74 -0
- package/src/plugins/api/auth/strategies/factory.class.js +63 -0
- package/src/plugins/api/auth/strategies/global-strategy.class.js +44 -0
- package/src/plugins/api/auth/strategies/path-based-strategy.class.js +83 -0
- package/src/plugins/api/auth/strategies/path-rules-strategy.class.js +118 -0
- package/src/plugins/api/concerns/failban-manager.js +106 -57
- package/src/plugins/api/concerns/opengraph-helper.js +116 -0
- package/src/plugins/api/concerns/route-context.js +601 -0
- package/src/plugins/api/concerns/state-machine.js +288 -0
- package/src/plugins/api/index.js +180 -41
- package/src/plugins/api/routes/auth-routes.js +198 -30
- package/src/plugins/api/routes/resource-routes.js +19 -4
- package/src/plugins/api/server/health-manager.class.js +163 -0
- package/src/plugins/api/server/middleware-chain.class.js +310 -0
- package/src/plugins/api/server/router.class.js +472 -0
- package/src/plugins/api/server.js +280 -1303
- package/src/plugins/api/utils/custom-routes.js +17 -5
- package/src/plugins/api/utils/guards.js +76 -17
- package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
- package/src/plugins/api/utils/openapi-generator.js +7 -6
- package/src/plugins/api/utils/template-engine.js +77 -3
- package/src/plugins/audit.plugin.js +30 -8
- package/src/plugins/backup.plugin.js +110 -14
- package/src/plugins/cache/cache.class.js +22 -5
- package/src/plugins/cache/filesystem-cache.class.js +116 -19
- package/src/plugins/cache/memory-cache.class.js +211 -57
- package/src/plugins/cache/multi-tier-cache.class.js +371 -0
- package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
- package/src/plugins/cache/redis-cache.class.js +552 -0
- package/src/plugins/cache/s3-cache.class.js +17 -8
- package/src/plugins/cache.plugin.js +176 -61
- package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
- package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
- package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
- package/src/plugins/cloud-inventory/index.js +29 -8
- package/src/plugins/cloud-inventory/registry.js +64 -42
- package/src/plugins/cloud-inventory.plugin.js +240 -138
- package/src/plugins/concerns/plugin-dependencies.js +54 -0
- package/src/plugins/concerns/resource-names.js +100 -0
- package/src/plugins/consumers/index.js +10 -2
- package/src/plugins/consumers/sqs-consumer.js +12 -2
- package/src/plugins/cookie-farm-suite.plugin.js +278 -0
- package/src/plugins/cookie-farm.errors.js +73 -0
- package/src/plugins/cookie-farm.plugin.js +869 -0
- package/src/plugins/costs.plugin.js +7 -1
- package/src/plugins/eventual-consistency/analytics.js +94 -19
- package/src/plugins/eventual-consistency/config.js +15 -7
- package/src/plugins/eventual-consistency/consolidation.js +29 -11
- package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
- package/src/plugins/eventual-consistency/helpers.js +39 -14
- package/src/plugins/eventual-consistency/install.js +21 -2
- package/src/plugins/eventual-consistency/utils.js +32 -10
- package/src/plugins/fulltext.plugin.js +38 -11
- package/src/plugins/geo.plugin.js +61 -9
- package/src/plugins/identity/concerns/config.js +61 -0
- package/src/plugins/identity/concerns/mfa-manager.js +15 -2
- package/src/plugins/identity/concerns/rate-limit.js +124 -0
- package/src/plugins/identity/concerns/resource-schemas.js +9 -1
- package/src/plugins/identity/concerns/token-generator.js +29 -4
- package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
- package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
- package/src/plugins/identity/drivers/index.js +18 -0
- package/src/plugins/identity/drivers/password-driver.js +122 -0
- package/src/plugins/identity/email-service.js +17 -2
- package/src/plugins/identity/index.js +413 -69
- package/src/plugins/identity/oauth2-server.js +413 -30
- package/src/plugins/identity/oidc-discovery.js +16 -8
- package/src/plugins/identity/rsa-keys.js +115 -35
- package/src/plugins/identity/server.js +166 -45
- package/src/plugins/identity/session-manager.js +53 -7
- package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
- package/src/plugins/identity/ui/routes.js +363 -255
- package/src/plugins/importer/index.js +153 -20
- package/src/plugins/index.js +9 -2
- package/src/plugins/kubernetes-inventory/index.js +6 -0
- package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
- package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
- package/src/plugins/kubernetes-inventory.plugin.js +980 -0
- package/src/plugins/metrics.plugin.js +64 -16
- package/src/plugins/ml/base-model.class.js +25 -15
- package/src/plugins/ml/regression-model.class.js +1 -1
- package/src/plugins/ml.errors.js +57 -25
- package/src/plugins/ml.plugin.js +28 -4
- package/src/plugins/namespace.js +210 -0
- package/src/plugins/plugin.class.js +180 -8
- package/src/plugins/puppeteer/console-monitor.js +729 -0
- package/src/plugins/puppeteer/cookie-manager.js +492 -0
- package/src/plugins/puppeteer/network-monitor.js +816 -0
- package/src/plugins/puppeteer/performance-manager.js +746 -0
- package/src/plugins/puppeteer/proxy-manager.js +478 -0
- package/src/plugins/puppeteer/stealth-manager.js +556 -0
- package/src/plugins/puppeteer.errors.js +81 -0
- package/src/plugins/puppeteer.plugin.js +1327 -0
- package/src/plugins/queue-consumer.plugin.js +69 -14
- package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
- package/src/plugins/recon/concerns/command-runner.js +148 -0
- package/src/plugins/recon/concerns/diff-detector.js +372 -0
- package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
- package/src/plugins/recon/concerns/process-manager.js +338 -0
- package/src/plugins/recon/concerns/report-generator.js +478 -0
- package/src/plugins/recon/concerns/security-analyzer.js +571 -0
- package/src/plugins/recon/concerns/target-normalizer.js +68 -0
- package/src/plugins/recon/config/defaults.js +321 -0
- package/src/plugins/recon/config/resources.js +370 -0
- package/src/plugins/recon/index.js +778 -0
- package/src/plugins/recon/managers/dependency-manager.js +174 -0
- package/src/plugins/recon/managers/scheduler-manager.js +179 -0
- package/src/plugins/recon/managers/storage-manager.js +745 -0
- package/src/plugins/recon/managers/target-manager.js +274 -0
- package/src/plugins/recon/stages/asn-stage.js +314 -0
- package/src/plugins/recon/stages/certificate-stage.js +84 -0
- package/src/plugins/recon/stages/dns-stage.js +107 -0
- package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
- package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
- package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
- package/src/plugins/recon/stages/http-stage.js +89 -0
- package/src/plugins/recon/stages/latency-stage.js +148 -0
- package/src/plugins/recon/stages/massdns-stage.js +302 -0
- package/src/plugins/recon/stages/osint-stage.js +1373 -0
- package/src/plugins/recon/stages/ports-stage.js +169 -0
- package/src/plugins/recon/stages/screenshot-stage.js +94 -0
- package/src/plugins/recon/stages/secrets-stage.js +514 -0
- package/src/plugins/recon/stages/subdomains-stage.js +295 -0
- package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
- package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
- package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
- package/src/plugins/recon/stages/whois-stage.js +349 -0
- package/src/plugins/recon.plugin.js +75 -0
- package/src/plugins/recon.plugin.js.backup +2635 -0
- package/src/plugins/relation.errors.js +87 -14
- package/src/plugins/replicator.plugin.js +514 -137
- package/src/plugins/replicators/base-replicator.class.js +89 -1
- package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
- package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mysql-replicator.class.js +52 -17
- package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
- package/src/plugins/replicators/postgres-replicator.class.js +62 -27
- package/src/plugins/replicators/s3db-replicator.class.js +25 -18
- package/src/plugins/replicators/schema-sync.helper.js +3 -3
- package/src/plugins/replicators/sqs-replicator.class.js +8 -2
- package/src/plugins/replicators/turso-replicator.class.js +23 -3
- package/src/plugins/replicators/webhook-replicator.class.js +42 -4
- package/src/plugins/s3-queue.plugin.js +464 -65
- package/src/plugins/scheduler.plugin.js +20 -6
- package/src/plugins/state-machine.plugin.js +40 -9
- package/src/plugins/tfstate/README.md +126 -126
- package/src/plugins/tfstate/base-driver.js +28 -4
- package/src/plugins/tfstate/errors.js +65 -10
- package/src/plugins/tfstate/filesystem-driver.js +52 -8
- package/src/plugins/tfstate/index.js +163 -90
- package/src/plugins/tfstate/s3-driver.js +64 -6
- package/src/plugins/ttl.plugin.js +72 -17
- package/src/plugins/vector/distances.js +18 -12
- package/src/plugins/vector/kmeans.js +26 -4
- package/src/resource.class.js +115 -19
- package/src/testing/factory.class.js +20 -3
- package/src/testing/seeder.class.js +7 -1
- package/src/clients/memory-client.md +0 -917
- package/src/plugins/cloud-inventory/drivers/mock-drivers.js +0 -449
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { asyncHandler } from './error-handler.js';
|
|
9
|
+
import { withContext, autoWrapHandler } from '../concerns/route-context.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Parse route definition from key
|
|
@@ -31,30 +32,41 @@ export function parseRouteKey(key) {
|
|
|
31
32
|
* @param {Object} routes - Routes object { 'METHOD /path': handler }
|
|
32
33
|
* @param {Object} context - Context to pass to handlers (resource, database, etc.)
|
|
33
34
|
* @param {boolean} verbose - Enable verbose logging
|
|
35
|
+
* @param {Object} options - Additional options
|
|
36
|
+
* @param {boolean} options.autoWrap - Auto-wrap handlers with enhanced context (default: true)
|
|
34
37
|
*/
|
|
35
|
-
export function mountCustomRoutes(app, routes, context = {}, verbose = false) {
|
|
38
|
+
export function mountCustomRoutes(app, routes, context = {}, verbose = false, options = {}) {
|
|
36
39
|
if (!routes || typeof routes !== 'object') {
|
|
37
40
|
return;
|
|
38
41
|
}
|
|
39
42
|
|
|
43
|
+
const { autoWrap = true } = options;
|
|
44
|
+
|
|
40
45
|
for (const [key, handler] of Object.entries(routes)) {
|
|
41
46
|
try {
|
|
42
47
|
const { method, path } = parseRouteKey(key);
|
|
43
48
|
|
|
44
49
|
// Wrap handler with async error handler and context
|
|
45
50
|
const wrappedHandler = asyncHandler(async (c) => {
|
|
46
|
-
// Inject context into Hono context
|
|
51
|
+
// Inject legacy context into Hono context (for backward compatibility)
|
|
47
52
|
c.set('customRouteContext', context);
|
|
48
53
|
|
|
49
|
-
//
|
|
50
|
-
|
|
54
|
+
// Auto-wrap handler if enabled and handler expects 2 arguments (c, ctx)
|
|
55
|
+
if (autoWrap && handler.length === 2) {
|
|
56
|
+
// Handler expects (c, ctx) - wrap it with enhanced context
|
|
57
|
+
return await withContext(handler, { resource: context.resource })(c);
|
|
58
|
+
} else {
|
|
59
|
+
// Handler expects only (c) - use legacy behavior
|
|
60
|
+
return await handler(c);
|
|
61
|
+
}
|
|
51
62
|
});
|
|
52
63
|
|
|
53
64
|
// Mount route
|
|
54
65
|
app.on(method, path, wrappedHandler);
|
|
55
66
|
|
|
56
67
|
if (verbose) {
|
|
57
|
-
|
|
68
|
+
const contextType = (autoWrap && handler.length === 2) ? '(enhanced)' : '(legacy)';
|
|
69
|
+
console.log(`[Custom Routes] Mounted ${method} ${path} ${contextType}`);
|
|
58
70
|
}
|
|
59
71
|
} catch (err) {
|
|
60
72
|
console.error(`[Custom Routes] Error mounting route "${key}":`, err.message);
|
|
@@ -3,21 +3,53 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Guards determine if a user can perform an operation on a resource.
|
|
5
5
|
* Supports: functions, scopes, roles, and combined logic.
|
|
6
|
+
*
|
|
7
|
+
* NEW: Guards can now receive full RouteContext for access to:
|
|
8
|
+
* - ctx.user, ctx.resources, ctx.param(), ctx.query()
|
|
9
|
+
* - ctx.setPartition() for tenant isolation
|
|
6
10
|
*/
|
|
7
11
|
|
|
8
12
|
/**
|
|
9
|
-
* Check if
|
|
10
|
-
*
|
|
13
|
+
* Check if guard passes (NEW: supports RouteContext)
|
|
14
|
+
*
|
|
15
|
+
* @param {Object|RouteContext} ctxOrUser - RouteContext (new) or user object (legacy)
|
|
11
16
|
* @param {Function|string|Array|Object|null} guard - Guard configuration
|
|
12
|
-
* @param {Object}
|
|
17
|
+
* @param {Object|null} recordOrContext - Record being accessed (update/delete) or legacy context
|
|
13
18
|
* @returns {boolean} True if authorized
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* // NEW: Guard with RouteContext
|
|
22
|
+
* users.guard = {
|
|
23
|
+
* list: (ctx) => {
|
|
24
|
+
* if (ctx.user.scopes?.includes('preset:admin')) return true;
|
|
25
|
+
* ctx.setPartition('byUserId', { userId: ctx.user.id });
|
|
26
|
+
* return true;
|
|
27
|
+
* }
|
|
28
|
+
* };
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // LEGACY: Guard with user object (still works!)
|
|
32
|
+
* users.guard = {
|
|
33
|
+
* list: (user, context) => {
|
|
34
|
+
* return user.scopes?.includes('preset:admin');
|
|
35
|
+
* }
|
|
36
|
+
* };
|
|
14
37
|
*/
|
|
15
|
-
export function checkGuard(
|
|
38
|
+
export function checkGuard(ctxOrUser, guard, recordOrContext = null) {
|
|
16
39
|
// No guard = public access
|
|
17
40
|
if (!guard) {
|
|
18
41
|
return true;
|
|
19
42
|
}
|
|
20
43
|
|
|
44
|
+
// Detect if first param is RouteContext (has .user property and ._currentResource)
|
|
45
|
+
const isRouteContext = ctxOrUser && typeof ctxOrUser === 'object' &&
|
|
46
|
+
('user' in ctxOrUser || '_currentResource' in ctxOrUser);
|
|
47
|
+
|
|
48
|
+
const ctx = isRouteContext ? ctxOrUser : null;
|
|
49
|
+
const user = isRouteContext ? ctxOrUser.user : ctxOrUser;
|
|
50
|
+
const record = isRouteContext ? recordOrContext : null;
|
|
51
|
+
const legacyContext = isRouteContext ? {} : (recordOrContext || {});
|
|
52
|
+
|
|
21
53
|
// No user = unauthorized (unless guard explicitly allows)
|
|
22
54
|
if (!user && guard !== true) {
|
|
23
55
|
return false;
|
|
@@ -28,10 +60,23 @@ export function checkGuard(user, guard, context = {}) {
|
|
|
28
60
|
return guard;
|
|
29
61
|
}
|
|
30
62
|
|
|
31
|
-
// Guard is function
|
|
63
|
+
// Guard is function
|
|
32
64
|
if (typeof guard === 'function') {
|
|
33
65
|
try {
|
|
34
|
-
|
|
66
|
+
// NEW: Pass RouteContext if available, else legacy (user, context) signature
|
|
67
|
+
if (ctx) {
|
|
68
|
+
// Detect guard signature: (ctx, record) or (ctx)
|
|
69
|
+
const guardLength = guard.length;
|
|
70
|
+
|
|
71
|
+
if (guardLength >= 2 && record !== null) {
|
|
72
|
+
return guard(ctx, record); // (ctx, record) for update/delete
|
|
73
|
+
} else {
|
|
74
|
+
return guard(ctx); // (ctx) for list/create
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
// LEGACY: (user, context) signature
|
|
78
|
+
return guard(user, legacyContext);
|
|
79
|
+
}
|
|
35
80
|
} catch (err) {
|
|
36
81
|
console.error('[Guards] Error executing guard function:', err);
|
|
37
82
|
return false;
|
|
@@ -72,7 +117,11 @@ export function checkGuard(user, guard, context = {}) {
|
|
|
72
117
|
// Check custom function
|
|
73
118
|
if (guard.check && typeof guard.check === 'function') {
|
|
74
119
|
try {
|
|
75
|
-
|
|
120
|
+
if (ctx) {
|
|
121
|
+
return guard.check(ctx, record);
|
|
122
|
+
} else {
|
|
123
|
+
return guard.check(user, legacyContext);
|
|
124
|
+
}
|
|
76
125
|
} catch (err) {
|
|
77
126
|
console.error('[Guards] Error executing guard.check function:', err);
|
|
78
127
|
return false;
|
|
@@ -169,22 +218,27 @@ export function getOperationGuard(guards, operation) {
|
|
|
169
218
|
}
|
|
170
219
|
|
|
171
220
|
/**
|
|
172
|
-
* Create guard middleware for Hono
|
|
221
|
+
* Create guard middleware for Hono (NEW: with RouteContext)
|
|
173
222
|
* @param {Object} guards - Guards configuration
|
|
174
223
|
* @param {string} operation - Operation name
|
|
224
|
+
* @param {Object} options - Options { resource, database, plugins }
|
|
175
225
|
* @returns {Function} Hono middleware
|
|
176
226
|
*/
|
|
177
|
-
export function guardMiddleware(guards, operation) {
|
|
227
|
+
export function guardMiddleware(guards, operation, options = {}) {
|
|
178
228
|
return async (c, next) => {
|
|
179
|
-
|
|
229
|
+
// Import RouteContext dynamically to avoid circular dependency
|
|
230
|
+
const { RouteContext } = await import('../concerns/route-context.js');
|
|
231
|
+
|
|
232
|
+
const legacyContext = c.get('customRouteContext') || {};
|
|
233
|
+
const { database, resource, plugins = {} } = { ...legacyContext, ...options };
|
|
234
|
+
|
|
235
|
+
// Create RouteContext for guard
|
|
236
|
+
const ctx = new RouteContext(c, database, resource, plugins);
|
|
237
|
+
|
|
180
238
|
const guard = getOperationGuard(guards, operation);
|
|
181
239
|
|
|
182
|
-
// Check guard
|
|
183
|
-
const authorized = checkGuard(
|
|
184
|
-
operation,
|
|
185
|
-
resourceName: c.req.param('resource'),
|
|
186
|
-
data: c.req.method !== 'GET' ? await c.req.json().catch(() => ({})) : {}
|
|
187
|
-
});
|
|
240
|
+
// Check guard (pass RouteContext)
|
|
241
|
+
const authorized = checkGuard(ctx, guard, null);
|
|
188
242
|
|
|
189
243
|
if (!authorized) {
|
|
190
244
|
return c.json({
|
|
@@ -194,13 +248,18 @@ export function guardMiddleware(guards, operation) {
|
|
|
194
248
|
code: 'FORBIDDEN',
|
|
195
249
|
details: {
|
|
196
250
|
operation,
|
|
197
|
-
user: user ? { id: user.id, role: user.role } : null
|
|
251
|
+
user: ctx.user ? { id: ctx.user.id, role: ctx.user.role } : null
|
|
198
252
|
}
|
|
199
253
|
},
|
|
200
254
|
_status: 403
|
|
201
255
|
}, 403);
|
|
202
256
|
}
|
|
203
257
|
|
|
258
|
+
// Store partition filters in context for use by route handlers
|
|
259
|
+
if (ctx.hasPartitionFilters()) {
|
|
260
|
+
c.set('partitionFilters', ctx.getPartitionFilters());
|
|
261
|
+
}
|
|
262
|
+
|
|
204
263
|
await next();
|
|
205
264
|
};
|
|
206
265
|
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAPIGeneratorCached - Smart caching wrapper for OpenAPI generation
|
|
3
|
+
*
|
|
4
|
+
* Caches OpenAPI spec and invalidates only when schema changes:
|
|
5
|
+
* - Resource changes (add/remove/modify)
|
|
6
|
+
* - Auth configuration changes
|
|
7
|
+
* - Version changes
|
|
8
|
+
*
|
|
9
|
+
* Performance: 0ms cache hits vs ~50-200ms generation
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { generateOpenAPISpec } from './openapi-generator.js';
|
|
13
|
+
import { createHash } from 'crypto';
|
|
14
|
+
|
|
15
|
+
export class OpenAPIGeneratorCached {
|
|
16
|
+
constructor({ database, options }) {
|
|
17
|
+
this.database = database;
|
|
18
|
+
this.options = options;
|
|
19
|
+
|
|
20
|
+
this.cache = null;
|
|
21
|
+
this.cacheKey = null;
|
|
22
|
+
|
|
23
|
+
if (options.verbose) {
|
|
24
|
+
console.log('[OpenAPIGenerator] Caching enabled');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Generate OpenAPI spec (with caching)
|
|
30
|
+
* @returns {Object} OpenAPI specification
|
|
31
|
+
*/
|
|
32
|
+
generate() {
|
|
33
|
+
// Check if cache is valid
|
|
34
|
+
const currentKey = this.generateCacheKey();
|
|
35
|
+
|
|
36
|
+
if (this.cacheKey === currentKey && this.cache) {
|
|
37
|
+
if (this.options.verbose) {
|
|
38
|
+
console.log('[OpenAPIGenerator] Cache HIT (0ms)');
|
|
39
|
+
}
|
|
40
|
+
return this.cache;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Cache miss - regenerate
|
|
44
|
+
if (this.options.verbose) {
|
|
45
|
+
const reason = !this.cache ? 'initial' : 'invalidated';
|
|
46
|
+
console.log(`[OpenAPIGenerator] Cache MISS (${reason})`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const startTime = Date.now();
|
|
50
|
+
this.cache = generateOpenAPISpec(this.database, this.options);
|
|
51
|
+
this.cacheKey = currentKey;
|
|
52
|
+
|
|
53
|
+
if (this.options.verbose) {
|
|
54
|
+
const duration = Date.now() - startTime;
|
|
55
|
+
console.log(`[OpenAPIGenerator] Generated spec in ${duration}ms`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return this.cache;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Generate cache key based on schema state
|
|
63
|
+
* @private
|
|
64
|
+
* @returns {string} Cache key (hash)
|
|
65
|
+
*/
|
|
66
|
+
generateCacheKey() {
|
|
67
|
+
// Components that affect OpenAPI spec
|
|
68
|
+
const components = {
|
|
69
|
+
// Resource names and versions
|
|
70
|
+
resources: Object.keys(this.database.resources).map(name => {
|
|
71
|
+
const resource = this.database.resources[name];
|
|
72
|
+
return {
|
|
73
|
+
name,
|
|
74
|
+
version: resource.config?.currentVersion || resource.version || 'v1',
|
|
75
|
+
// Shallow hash of attributes (type changes invalidate cache)
|
|
76
|
+
attributes: Object.keys(resource.attributes || {}).sort().join(',')
|
|
77
|
+
};
|
|
78
|
+
}),
|
|
79
|
+
|
|
80
|
+
// Auth configuration affects security schemes
|
|
81
|
+
auth: {
|
|
82
|
+
drivers: this.options.auth?.drivers?.map(d => d.driver).sort() || [],
|
|
83
|
+
pathRules: this.options.auth?.pathRules?.length || 0,
|
|
84
|
+
pathAuth: !!this.options.auth?.pathAuth
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
// Resource config affects paths
|
|
88
|
+
resourceConfig: Object.keys(this.options.resources || {}).sort(),
|
|
89
|
+
|
|
90
|
+
// Version prefix affects URLs
|
|
91
|
+
versionPrefix: this.options.versionPrefix,
|
|
92
|
+
|
|
93
|
+
// API info
|
|
94
|
+
apiInfo: {
|
|
95
|
+
title: this.options.title,
|
|
96
|
+
version: this.options.version,
|
|
97
|
+
description: this.options.description
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// Hash the components
|
|
102
|
+
const hash = createHash('sha256')
|
|
103
|
+
.update(JSON.stringify(components))
|
|
104
|
+
.digest('hex')
|
|
105
|
+
.substring(0, 16); // First 16 chars sufficient for cache key
|
|
106
|
+
|
|
107
|
+
return hash;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Invalidate cache (force regeneration on next request)
|
|
112
|
+
*/
|
|
113
|
+
invalidate() {
|
|
114
|
+
this.cache = null;
|
|
115
|
+
this.cacheKey = null;
|
|
116
|
+
|
|
117
|
+
if (this.options.verbose) {
|
|
118
|
+
console.log('[OpenAPIGenerator] Cache manually invalidated');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get cache statistics
|
|
124
|
+
* @returns {Object} Cache stats
|
|
125
|
+
*/
|
|
126
|
+
getStats() {
|
|
127
|
+
return {
|
|
128
|
+
cached: !!this.cache,
|
|
129
|
+
cacheKey: this.cacheKey,
|
|
130
|
+
size: this.cache ? JSON.stringify(this.cache).length : 0
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -98,12 +98,13 @@ function generateResourceSchema(resource) {
|
|
|
98
98
|
const properties = {};
|
|
99
99
|
const required = [];
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
// Use $schema for reliable access to resource definition
|
|
102
|
+
const allAttributes = resource.$schema.attributes || {};
|
|
102
103
|
|
|
103
104
|
// Filter out plugin attributes - they are internal implementation details
|
|
104
105
|
// and should not be exposed in public API documentation
|
|
105
|
-
const pluginAttrNames = resource.
|
|
106
|
-
? Object.values(resource
|
|
106
|
+
const pluginAttrNames = resource.$schema._pluginAttributes
|
|
107
|
+
? Object.values(resource.$schema._pluginAttributes).flat()
|
|
107
108
|
: [];
|
|
108
109
|
|
|
109
110
|
const attributes = Object.fromEntries(
|
|
@@ -111,7 +112,7 @@ function generateResourceSchema(resource) {
|
|
|
111
112
|
);
|
|
112
113
|
|
|
113
114
|
// Extract resource description (supports both string and object format)
|
|
114
|
-
const resourceDescription = resource.
|
|
115
|
+
const resourceDescription = resource.$schema.description;
|
|
115
116
|
const attributeDescriptions = typeof resourceDescription === 'object'
|
|
116
117
|
? (resourceDescription.attributes || {})
|
|
117
118
|
: {};
|
|
@@ -215,8 +216,8 @@ function generateResourcePaths(resource, version, config = {}) {
|
|
|
215
216
|
}
|
|
216
217
|
|
|
217
218
|
// Extract partition information for documentation
|
|
218
|
-
//
|
|
219
|
-
const partitions = resource.
|
|
219
|
+
// Use $schema for reliable access to partition definitions
|
|
220
|
+
const partitions = resource.$schema.partitions || {};
|
|
220
221
|
const partitionNames = Object.keys(partitions);
|
|
221
222
|
const hasPartitions = partitionNames.length > 0;
|
|
222
223
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides c.render() helper that works with multiple template engines:
|
|
5
5
|
* - EJS (for mrt-shortner compatibility)
|
|
6
|
+
* - Pug (formerly Jade)
|
|
6
7
|
* - JSX (Hono native)
|
|
7
8
|
* - Custom engines via setRenderer()
|
|
8
9
|
*
|
|
@@ -19,6 +20,17 @@
|
|
|
19
20
|
* });
|
|
20
21
|
*
|
|
21
22
|
* @example
|
|
23
|
+
* // Pug usage
|
|
24
|
+
* app.use('*', setupTemplateEngine({
|
|
25
|
+
* engine: 'pug',
|
|
26
|
+
* templatesDir: './views'
|
|
27
|
+
* }));
|
|
28
|
+
*
|
|
29
|
+
* app.get('/page', async (c) => {
|
|
30
|
+
* return c.render('landing', { urlCount: 1000 });
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
22
34
|
* // JSX usage (no setup needed)
|
|
23
35
|
* app.get('/page', (c) => {
|
|
24
36
|
* return c.render(<h1>Hello</h1>);
|
|
@@ -45,11 +57,27 @@ async function loadEJS() {
|
|
|
45
57
|
}
|
|
46
58
|
}
|
|
47
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Lazy-load Pug (peer dependency)
|
|
62
|
+
* @returns {Promise<Object>} Pug module
|
|
63
|
+
*/
|
|
64
|
+
async function loadPug() {
|
|
65
|
+
try {
|
|
66
|
+
const pug = await import('pug');
|
|
67
|
+
return pug.default || pug;
|
|
68
|
+
} catch (err) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
'Pug template engine not installed. Install with: npm install pug\n' +
|
|
71
|
+
'Pug is a peer dependency to keep the core package lightweight.'
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
48
76
|
/**
|
|
49
77
|
* Setup template engine middleware
|
|
50
78
|
* @param {Object} options - Template engine options
|
|
51
|
-
* @param {string} options.engine - Engine name: 'ejs', 'jsx', 'custom'
|
|
52
|
-
* @param {string} options.templatesDir - Directory containing templates (required for EJS)
|
|
79
|
+
* @param {string} options.engine - Engine name: 'ejs', 'pug', 'jsx', 'custom'
|
|
80
|
+
* @param {string} options.templatesDir - Directory containing templates (required for EJS/Pug)
|
|
53
81
|
* @param {string} options.layout - Default layout template (optional for EJS)
|
|
54
82
|
* @param {Object} options.engineOptions - Additional engine-specific options
|
|
55
83
|
* @param {Function} options.customRenderer - Custom render function (for 'custom' engine)
|
|
@@ -70,7 +98,7 @@ export function setupTemplateEngine(options = {}) {
|
|
|
70
98
|
return async (c, next) => {
|
|
71
99
|
/**
|
|
72
100
|
* Render template with data
|
|
73
|
-
* @param {string|JSX.Element} template - Template name (for EJS) or JSX element
|
|
101
|
+
* @param {string|JSX.Element} template - Template name (for EJS/Pug) or JSX element
|
|
74
102
|
* @param {Object} data - Data to pass to template
|
|
75
103
|
* @param {Object} renderOptions - Render-specific options
|
|
76
104
|
* @returns {Response} HTML response
|
|
@@ -82,6 +110,37 @@ export function setupTemplateEngine(options = {}) {
|
|
|
82
110
|
return c.html(template);
|
|
83
111
|
}
|
|
84
112
|
|
|
113
|
+
// Pug: File-based rendering
|
|
114
|
+
if (engine === 'pug') {
|
|
115
|
+
// Lazy-load Pug
|
|
116
|
+
const pug = await loadPug();
|
|
117
|
+
|
|
118
|
+
const templateFile = template.endsWith('.pug') ? template : `${template}.pug`;
|
|
119
|
+
const templatePath = join(templatesPath, templateFile);
|
|
120
|
+
|
|
121
|
+
if (!existsSync(templatePath)) {
|
|
122
|
+
throw new Error(`Template not found: ${templatePath}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Merge global data + render data
|
|
126
|
+
const renderData = {
|
|
127
|
+
...data,
|
|
128
|
+
// Add helpers that Pug templates might expect
|
|
129
|
+
_url: c.req.url,
|
|
130
|
+
_path: c.req.path,
|
|
131
|
+
_method: c.req.method
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Render template
|
|
135
|
+
const html = pug.renderFile(templatePath, {
|
|
136
|
+
...renderData,
|
|
137
|
+
...engineOptions,
|
|
138
|
+
...renderOptions
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return c.html(html);
|
|
142
|
+
}
|
|
143
|
+
|
|
85
144
|
// EJS: File-based rendering
|
|
86
145
|
if (engine === 'ejs') {
|
|
87
146
|
// Lazy-load EJS
|
|
@@ -164,6 +223,20 @@ export function ejsEngine(templatesDir, options = {}) {
|
|
|
164
223
|
});
|
|
165
224
|
}
|
|
166
225
|
|
|
226
|
+
/**
|
|
227
|
+
* Create Pug template engine middleware (convenience wrapper)
|
|
228
|
+
* @param {string} templatesDir - Directory containing templates
|
|
229
|
+
* @param {Object} options - Additional options
|
|
230
|
+
* @returns {Function} Hono middleware
|
|
231
|
+
*/
|
|
232
|
+
export function pugEngine(templatesDir, options = {}) {
|
|
233
|
+
return setupTemplateEngine({
|
|
234
|
+
engine: 'pug',
|
|
235
|
+
templatesDir,
|
|
236
|
+
...options
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
167
240
|
/**
|
|
168
241
|
* Create JSX template engine middleware (convenience wrapper)
|
|
169
242
|
* Note: JSX rendering is built into Hono, this just provides c.render()
|
|
@@ -184,5 +257,6 @@ export function jsxEngine() {
|
|
|
184
257
|
export default {
|
|
185
258
|
setupTemplateEngine,
|
|
186
259
|
ejsEngine,
|
|
260
|
+
pugEngine,
|
|
187
261
|
jsxEngine
|
|
188
262
|
};
|
|
@@ -426,12 +426,24 @@
|
|
|
426
426
|
*/
|
|
427
427
|
|
|
428
428
|
import { Plugin } from "./plugin.class.js";
|
|
429
|
+
import { getValidatedNamespace } from "./namespace.js";
|
|
429
430
|
import tryFn from "../concerns/try-fn.js";
|
|
431
|
+
import { resolveResourceName } from "./concerns/resource-names.js";
|
|
430
432
|
|
|
431
433
|
export class AuditPlugin extends Plugin {
|
|
432
434
|
constructor(options = {}) {
|
|
433
435
|
super(options);
|
|
436
|
+
|
|
437
|
+
// Validate and set namespace (standardized)
|
|
438
|
+
this.namespace = getValidatedNamespace(options, '');
|
|
439
|
+
|
|
440
|
+
const resourceNames = options.resourceNames || {};
|
|
434
441
|
this.auditResource = null;
|
|
442
|
+
this._auditResourceDescriptor = {
|
|
443
|
+
defaultName: 'plg_audits',
|
|
444
|
+
override: resourceNames.audit || options.resourceName
|
|
445
|
+
};
|
|
446
|
+
this.auditResourceName = this._resolveAuditResourceName();
|
|
435
447
|
this.config = {
|
|
436
448
|
includeData: options.includeData !== false,
|
|
437
449
|
includePartitions: options.includePartitions !== false,
|
|
@@ -440,10 +452,20 @@ export class AuditPlugin extends Plugin {
|
|
|
440
452
|
};
|
|
441
453
|
}
|
|
442
454
|
|
|
455
|
+
_resolveAuditResourceName() {
|
|
456
|
+
return resolveResourceName('audit', this._auditResourceDescriptor, {
|
|
457
|
+
namespace: this.namespace
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
onNamespaceChanged() {
|
|
462
|
+
this.auditResourceName = this._resolveAuditResourceName();
|
|
463
|
+
}
|
|
464
|
+
|
|
443
465
|
async onInstall() {
|
|
444
466
|
// Create audit resource
|
|
445
467
|
const [ok, err, auditResource] = await tryFn(() => this.database.createResource({
|
|
446
|
-
name:
|
|
468
|
+
name: this.auditResourceName,
|
|
447
469
|
attributes: {
|
|
448
470
|
id: 'string|required',
|
|
449
471
|
resourceName: 'string|required',
|
|
@@ -464,19 +486,19 @@ export class AuditPlugin extends Plugin {
|
|
|
464
486
|
},
|
|
465
487
|
behavior: 'body-overflow'
|
|
466
488
|
}));
|
|
467
|
-
this.auditResource = ok ? auditResource : (this.database.resources.
|
|
489
|
+
this.auditResource = ok ? auditResource : (this.database.resources[this.auditResourceName] || null);
|
|
468
490
|
if (!ok && !this.auditResource) return;
|
|
469
491
|
|
|
470
492
|
// Hook into database for new resources
|
|
471
493
|
this.database.addHook('afterCreateResource', (context) => {
|
|
472
|
-
if (context.resource.name !==
|
|
494
|
+
if (context.resource.name !== this.auditResourceName) {
|
|
473
495
|
this.setupResourceAuditing(context.resource);
|
|
474
496
|
}
|
|
475
497
|
});
|
|
476
498
|
|
|
477
499
|
// Setup existing resources
|
|
478
500
|
for (const resource of Object.values(this.database.resources)) {
|
|
479
|
-
if (resource.name !==
|
|
501
|
+
if (resource.name !== this.auditResourceName) {
|
|
480
502
|
this.setupResourceAuditing(resource);
|
|
481
503
|
}
|
|
482
504
|
}
|
|
@@ -630,9 +652,9 @@ export class AuditPlugin extends Plugin {
|
|
|
630
652
|
|
|
631
653
|
getPartitionValues(data, resource) {
|
|
632
654
|
if (!this.config.includePartitions) return null;
|
|
633
|
-
|
|
634
|
-
//
|
|
635
|
-
const partitions = resource.
|
|
655
|
+
|
|
656
|
+
// Use $schema for reliable access to partition definitions
|
|
657
|
+
const partitions = resource.$schema.partitions;
|
|
636
658
|
if (!partitions) {
|
|
637
659
|
return null;
|
|
638
660
|
}
|
|
@@ -847,4 +869,4 @@ export class AuditPlugin extends Plugin {
|
|
|
847
869
|
|
|
848
870
|
return deletedCount;
|
|
849
871
|
}
|
|
850
|
-
}
|
|
872
|
+
}
|