webspresso 0.0.104 → 0.0.106
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/.agents/AGENTS.md +21 -2
- package/.agents/ARCHITECTURE.md +23 -0
- package/.agents/CACHE.md +147 -0
- package/.agents/CLI.md +220 -8
- package/.agents/EARLY_HINTS.md +120 -0
- package/.agents/FLAGS.md +118 -0
- package/.agents/HEALTH.md +118 -0
- package/.agents/LIMITS.md +109 -0
- package/.agents/LOGGING.md +93 -0
- package/.agents/METRICS.md +127 -0
- package/.agents/MIGRATION_JOBS.md +95 -0
- package/.agents/ORM.md +155 -1
- package/.agents/PLUGINS.md +38 -3
- package/.agents/QUEUE.md +42 -0
- package/.agents/RATE_LIMIT.md +147 -0
- package/.agents/RESILIENCE.md +235 -0
- package/.agents/SCALE_OUT.md +109 -0
- package/.agents/SECRETS.md +117 -0
- package/AGENTS.md +16 -0
- package/README.md +2 -2
- package/bin/commands/add-aws.js +396 -0
- package/bin/commands/add-ci.js +523 -0
- package/bin/commands/add-cloudflare.js +240 -0
- package/bin/commands/add-cloudrun.js +381 -0
- package/bin/commands/add-docker.js +6 -6
- package/bin/commands/add-migration-job.js +396 -0
- package/bin/commands/assets-sync.js +209 -0
- package/bin/commands/client-generate.js +52 -0
- package/bin/commands/console.js +53 -0
- package/bin/commands/db-migrate.js +47 -2
- package/bin/commands/db-studio.js +84 -0
- package/bin/commands/deploy-check.js +242 -13
- package/bin/commands/dev.js +1 -1
- package/bin/commands/export.js +87 -0
- package/bin/commands/new.js +5 -5
- package/bin/commands/openapi.js +1 -1
- package/bin/commands/secrets-pull.js +208 -0
- package/bin/commands/start.js +1 -1
- package/bin/utils/db.js +7 -1
- package/bin/utils/project.js +2 -2
- package/bin/webspresso.js +22 -0
- package/core/app.js +421 -0
- package/core/auth/authenticate.js +93 -0
- package/core/auth/hash.js +5 -4
- package/core/auth/index.js +34 -4
- package/core/auth/jwt.js +5 -54
- package/core/auth/magic-link.js +4 -5
- package/core/auth/manager.js +53 -10
- package/core/auth/middleware.js +16 -68
- package/core/auth/oauth.js +8 -17
- package/core/auth/redis-session.js +1008 -0
- package/core/auth/sessions.js +156 -0
- package/core/auth/totp.js +2 -1
- package/core/cache/byte-size.js +123 -0
- package/core/cache/cdn.js +296 -0
- package/core/cache/index.js +33 -0
- package/core/cache/lru.js +477 -0
- package/core/cache/tagged-lru.js +354 -0
- package/core/commands/registry.js +6 -20
- package/core/console/commands.js +148 -0
- package/core/console/context.js +260 -0
- package/core/console/eval.js +70 -0
- package/core/console/index.js +22 -0
- package/core/console/repl.js +102 -0
- package/core/early-hints/index.js +267 -0
- package/core/errors/domain.js +49 -0
- package/core/errors/http.js +31 -0
- package/core/errors/index.js +10 -0
- package/core/errors/normalize.js +88 -2
- package/core/events/discovery.js +6 -24
- package/core/events/dispatcher.js +9 -7
- package/core/events/signal.js +7 -0
- package/core/export/discovery.js +191 -0
- package/core/export/exporter.js +277 -0
- package/core/export/index.js +24 -0
- package/core/flags/evaluator.js +252 -0
- package/core/flags/hasher.js +37 -0
- package/core/flags/index.js +21 -0
- package/core/flags/manager.js +263 -0
- package/core/flags/store.js +126 -0
- package/core/health/index.js +41 -0
- package/core/health/middleware.js +213 -0
- package/core/health/normalize.js +140 -0
- package/core/health/probes.js +203 -0
- package/core/http/adapter.js +74 -0
- package/core/http/express-adapter.js +46 -0
- package/core/http/hono-adapter.js +531 -0
- package/core/http/index.js +41 -0
- package/core/http/pipeline.js +485 -0
- package/core/index.js +336 -0
- package/core/limits/index.js +34 -0
- package/core/limits/middleware.js +325 -0
- package/core/limits/normalize.js +222 -0
- package/core/logger/formatters.js +264 -0
- package/core/logger/index.js +260 -0
- package/core/logger/trace.js +137 -0
- package/core/metrics/collectors.js +125 -0
- package/core/metrics/index.js +45 -0
- package/core/metrics/middleware.js +230 -0
- package/core/metrics/registry.js +366 -0
- package/core/orm/batch.js +70 -0
- package/core/orm/cache/fingerprint.js +2 -2
- package/core/orm/cache/memory-provider.js +27 -110
- package/core/orm/d1.js +265 -0
- package/core/orm/index.js +176 -13
- package/core/orm/json-fields.js +22 -8
- package/core/orm/migrate-cloud.js +194 -0
- package/core/orm/model.js +10 -0
- package/core/orm/pool.js +97 -0
- package/core/orm/query-builder.js +72 -9
- package/core/orm/repository.js +318 -19
- package/core/orm/schema-helpers.js +8 -10
- package/core/orm/scopes.js +321 -1
- package/core/orm/transaction.js +10 -2
- package/core/orm/utils.js +4 -86
- package/core/queue/adapters/cloudflare.js +131 -0
- package/core/queue/adapters/database.js +4 -8
- package/core/queue/adapters/pubsub.js +400 -0
- package/core/queue/adapters/sqs.js +419 -0
- package/core/queue/adapters/wait-until.js +77 -0
- package/core/queue/index.js +8 -0
- package/core/queue/manager.js +66 -41
- package/core/rate-limit/index.js +22 -0
- package/core/rate-limit/limiter.js +219 -0
- package/core/rate-limit/stores/memory.js +129 -0
- package/core/rate-limit/stores/redis.js +151 -0
- package/core/rate-limit/utils.js +112 -0
- package/core/resilience/circuit-breaker.js +422 -0
- package/core/resilience/errors.js +63 -0
- package/core/resilience/index.js +53 -0
- package/core/resilience/registry.js +97 -0
- package/core/resilience/retry.js +203 -0
- package/core/schema-cache.js +9 -2
- package/core/secrets/index.js +113 -0
- package/core/secrets/manager.js +308 -0
- package/core/secrets/providers/aws.js +288 -0
- package/core/secrets/providers/env.js +44 -0
- package/core/secrets/providers/gcp.js +307 -0
- package/core/secrets/providers/memory.js +76 -0
- package/core/shutdown/http-adapter.js +4 -1
- package/core/shutdown/shutdown-manager.js +129 -19
- package/core/stream/index.js +19 -0
- package/core/stream/text-stream.js +326 -0
- package/core/studio/index.js +43 -0
- package/core/studio/inspector.js +258 -0
- package/core/studio/server.js +325 -0
- package/core/studio/ui.js +681 -0
- package/core/utils/array.js +12 -0
- package/core/utils/base64url.js +8 -0
- package/core/utils/crypto.js +118 -0
- package/core/utils/ensure-leading-slash.js +6 -0
- package/core/utils/escape-regexp.js +7 -0
- package/core/utils/fetch.js +6 -5
- package/core/utils/fs.js +144 -0
- package/core/utils/hmac.js +6 -0
- package/core/utils/json.js +98 -0
- package/core/utils/object.js +106 -0
- package/core/utils/random.js +9 -0
- package/core/utils/regex.js +77 -0
- package/core/utils/sha256.js +6 -0
- package/core/utils/string.js +87 -0
- package/core/utils/strip-trailing-slash.js +6 -0
- package/core/utils/time.js +96 -0
- package/core/utils/timeout.js +348 -0
- package/core/utils/url.js +106 -0
- package/core/utils/walk.js +7 -0
- package/index.d.ts +1549 -2
- package/index.js +272 -2
- package/package.json +3 -2
- package/plugins/admin-panel/core/api-extensions.js +2 -5
- package/plugins/admin-panel/index.js +13 -6
- package/plugins/admin-panel/modules/menu.js +1 -1
- package/plugins/api-key/crypto.js +3 -3
- package/plugins/api-key/index.js +3 -2
- package/plugins/api-key/scopes.js +5 -7
- package/plugins/audit-log/api-handlers.js +6 -15
- package/plugins/audit-log/index.js +3 -1
- package/plugins/audit-log/parse.js +3 -3
- package/plugins/basic-auth/index.js +3 -2
- package/plugins/cors.js +3 -2
- package/plugins/csrf/index.js +9 -12
- package/plugins/csv/index.js +8 -5
- package/plugins/dashboard/app.js +2 -2
- package/plugins/dashboard/index.js +5 -5
- package/plugins/data-exchange/record-selection.js +2 -5
- package/plugins/email/api-handlers.js +5 -18
- package/plugins/email/auth-bridge.js +3 -2
- package/plugins/email/index.js +11 -9
- package/plugins/health-check.js +2 -1
- package/plugins/index.js +5 -0
- package/plugins/lazy.js +5 -6
- package/plugins/mcp/discovery.js +18 -17
- package/plugins/polar/src/api.js +2 -1
- package/plugins/polar/src/webhooks.js +5 -6
- package/plugins/queue/index.js +18 -2
- package/plugins/rate-limit/index.js +3 -1
- package/plugins/redirect/index.js +16 -9
- package/plugins/rls/index.js +170 -0
- package/plugins/seo-checker/index.js +2 -2
- package/plugins/site-analytics/index.js +3 -1
- package/plugins/site-analytics/tracking.js +2 -2
- package/plugins/sitemap.js +3 -2
- package/plugins/swagger.js +12 -5
- package/plugins/upload/gcs-provider.js +148 -0
- package/plugins/upload/index.js +136 -77
- package/plugins/upload/orphan-pruner.js +4 -3
- package/plugins/upload/s3-provider.js +257 -0
- package/plugins/webhooks/crypto.js +6 -11
- package/plugins/webhooks/dispatcher.js +8 -8
- package/plugins/webhooks/index.js +33 -23
- package/plugins/webhooks/matcher.js +7 -11
- package/plugins/webhooks/sender.js +8 -11
- package/plugins/xlsx/index.js +7 -5
- package/src/api/api-loader.js +12 -95
- package/src/api/define-api.js +1 -0
- package/src/app-context.js +41 -5
- package/src/client-runtime/constants.js +10 -0
- package/src/client-runtime/mount.js +2 -3
- package/src/dev/banner.js +6 -2
- package/src/dev/client-gen.js +458 -0
- package/src/discovery/scan-routes.js +5 -2
- package/src/file-router.js +3 -3
- package/src/helpers.js +212 -24
- package/src/modules/module-discovery.js +1 -9
- package/src/pages/page-loader.js +13 -154
- package/src/plugin-manager.js +99 -18
- package/src/server.js +335 -133
- package/src/services/discovery.js +19 -40
- package/src/services/executor.js +95 -25
- package/src/services/memoize.js +3 -25
- package/src/services/registry.js +15 -1
- package/src/services/validator.js +38 -20
- package/src/template/precompile.js +8 -19
|
@@ -249,9 +249,10 @@ function basicAuthPlugin(options = {}) {
|
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
// Mount global middleware if global: true
|
|
252
|
-
|
|
252
|
+
const target = ctx.http || ctx.app;
|
|
253
|
+
if (applyGlobal && target) {
|
|
253
254
|
const globalMw = createBasicAuthMiddleware(options);
|
|
254
|
-
|
|
255
|
+
target.use(globalMw);
|
|
255
256
|
}
|
|
256
257
|
},
|
|
257
258
|
};
|
package/plugins/cors.js
CHANGED
|
@@ -173,8 +173,9 @@ function corsPlugin(options = {}) {
|
|
|
173
173
|
ctx.middlewares.cors = corsMiddleware;
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
|
|
177
|
-
|
|
176
|
+
const target = ctx.http || ctx.app;
|
|
177
|
+
if (target && options.global !== false && typeof target.use === 'function') {
|
|
178
|
+
target.use(corsMiddleware);
|
|
178
179
|
}
|
|
179
180
|
},
|
|
180
181
|
};
|
package/plugins/csrf/index.js
CHANGED
|
@@ -15,7 +15,8 @@ const NOOP = () => {};
|
|
|
15
15
|
const EMPTY_OBJECT = Object.freeze({});
|
|
16
16
|
const ignoreRegexCache = new Map();
|
|
17
17
|
|
|
18
|
-
const { safeCompare } = require('../../core/utils/crypto');
|
|
18
|
+
const { safeCompare, randomToken } = require('../../core/utils/crypto');
|
|
19
|
+
const { globToRegExp } = require('../../core/utils/regex');
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Check if the request path matches any ignore patterns
|
|
@@ -35,12 +36,7 @@ function isIgnored(req, ignorePaths) {
|
|
|
35
36
|
if (pattern.includes('*')) {
|
|
36
37
|
let regex = ignoreRegexCache.get(pattern);
|
|
37
38
|
if (regex === undefined) {
|
|
38
|
-
|
|
39
|
-
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
40
|
-
.replace(/\*\*/g, '___GLOB_STAR_STAR___')
|
|
41
|
-
.replace(/\*/g, '[^/]*')
|
|
42
|
-
.replace(/___GLOB_STAR_STAR___/g, '.*');
|
|
43
|
-
regex = new RegExp(`^${regexPattern}$`);
|
|
39
|
+
regex = globToRegExp(pattern, { separator: '/' });
|
|
44
40
|
if (ignoreRegexCache.size < 500) {
|
|
45
41
|
ignoreRegexCache.set(pattern, regex);
|
|
46
42
|
}
|
|
@@ -149,7 +145,7 @@ function csrfPlugin(options = {}) {
|
|
|
149
145
|
if (useCookie) {
|
|
150
146
|
csrfTokenValue = cookieOptions.signed ? req.signedCookies[cookieKey] : req.cookies[cookieKey];
|
|
151
147
|
if (!csrfTokenValue) {
|
|
152
|
-
csrfTokenValue =
|
|
148
|
+
csrfTokenValue = randomToken(32, 'hex');
|
|
153
149
|
const isHttps = req.secure || req.headers?.['x-forwarded-proto'] === 'https' || process.env.NODE_ENV === 'production' || process.env.COOKIE_SECURE === 'true';
|
|
154
150
|
const effectiveCookieOptions = {
|
|
155
151
|
httpOnly: true,
|
|
@@ -162,7 +158,7 @@ function csrfPlugin(options = {}) {
|
|
|
162
158
|
} else {
|
|
163
159
|
// Session based
|
|
164
160
|
if (!req.session[sessionKey]) {
|
|
165
|
-
req.session[sessionKey] =
|
|
161
|
+
req.session[sessionKey] = randomToken(32, 'hex');
|
|
166
162
|
}
|
|
167
163
|
csrfTokenValue = req.session[sessionKey];
|
|
168
164
|
}
|
|
@@ -217,9 +213,10 @@ function csrfPlugin(options = {}) {
|
|
|
217
213
|
ctx.addHelper('csrfToken', csrfToken);
|
|
218
214
|
ctx.addHelper('csrfInput', csrfInput);
|
|
219
215
|
|
|
220
|
-
// If global validation is enabled, mount the middleware globally
|
|
221
|
-
|
|
222
|
-
|
|
216
|
+
// If global validation is enabled, mount the middleware globally
|
|
217
|
+
const target = ctx.http || ctx.app;
|
|
218
|
+
if (applyGlobal && target && typeof target.use === 'function') {
|
|
219
|
+
target.use(createMiddleware());
|
|
223
220
|
}
|
|
224
221
|
},
|
|
225
222
|
};
|
package/plugins/csv/index.js
CHANGED
|
@@ -55,12 +55,14 @@ function csvPlugin(options = {}) {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
// 2. Register built-in services (csv.generate, csv.parse, csv.exportModel)
|
|
58
|
-
|
|
58
|
+
// 2. Register built-in services (csv.generate, csv.parse, csv.exportModel)
|
|
59
|
+
const serviceRegistry = ctx.serviceRegistry || ctx.services || ctx.app?.serviceRegistry;
|
|
60
|
+
if (serviceRegistry) {
|
|
59
61
|
const db = dbOption ?? ctx.db ?? ctx.app?.get?.('db') ?? null;
|
|
60
62
|
const services = createCsvServices({ db, ...options });
|
|
61
63
|
for (const [name, def] of Object.entries(services)) {
|
|
62
|
-
if (!
|
|
63
|
-
|
|
64
|
+
if (!serviceRegistry.has(name)) {
|
|
65
|
+
serviceRegistry.register(name, def);
|
|
64
66
|
}
|
|
65
67
|
}
|
|
66
68
|
}
|
|
@@ -77,8 +79,9 @@ function csvPlugin(options = {}) {
|
|
|
77
79
|
ctx.middlewares.csv = middleware;
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
const target = ctx.http || ctx.app;
|
|
83
|
+
if (target && typeof target.use === 'function') {
|
|
84
|
+
target.use(middleware);
|
|
82
85
|
}
|
|
83
86
|
},
|
|
84
87
|
};
|
package/plugins/dashboard/app.js
CHANGED
|
@@ -363,11 +363,11 @@ const ConfigView = {
|
|
|
363
363
|
m('.table-container', [
|
|
364
364
|
m('.config-item', [
|
|
365
365
|
m('.config-key', 'Port'),
|
|
366
|
-
m('.config-value', config.server?.port || '
|
|
366
|
+
m('.config-value', config.server?.port || '7331')
|
|
367
367
|
]),
|
|
368
368
|
m('.config-item', [
|
|
369
369
|
m('.config-key', 'Base URL'),
|
|
370
|
-
m('.config-value', config.server?.baseUrl || 'http://localhost:
|
|
370
|
+
m('.config-value', config.server?.baseUrl || 'http://localhost:7331')
|
|
371
371
|
])
|
|
372
372
|
])
|
|
373
373
|
])
|
|
@@ -164,16 +164,16 @@ function dashboardPlugin(options = {}) {
|
|
|
164
164
|
const config = {
|
|
165
165
|
env: filterSensitiveEnv({
|
|
166
166
|
NODE_ENV: process.env.NODE_ENV || 'development',
|
|
167
|
-
PORT: process.env.PORT || '
|
|
168
|
-
BASE_URL: process.env.BASE_URL || 'http://localhost:
|
|
167
|
+
PORT: process.env.PORT || '7331',
|
|
168
|
+
BASE_URL: process.env.BASE_URL || 'http://localhost:7331',
|
|
169
169
|
}),
|
|
170
170
|
i18n: {
|
|
171
171
|
defaultLocale: process.env.DEFAULT_LOCALE || 'en',
|
|
172
172
|
supportedLocales: (process.env.SUPPORTED_LOCALES || 'en').split(','),
|
|
173
173
|
},
|
|
174
174
|
server: {
|
|
175
|
-
port: process.env.PORT || '
|
|
176
|
-
baseUrl: process.env.BASE_URL || 'http://localhost:
|
|
175
|
+
port: process.env.PORT || '7331',
|
|
176
|
+
baseUrl: process.env.BASE_URL || 'http://localhost:7331',
|
|
177
177
|
},
|
|
178
178
|
database: {
|
|
179
179
|
connected: hasDb(),
|
|
@@ -232,7 +232,7 @@ function dashboardPlugin(options = {}) {
|
|
|
232
232
|
});
|
|
233
233
|
|
|
234
234
|
// Log dashboard URL
|
|
235
|
-
console.log(`\n📊 Dashboard available at: http://localhost:${process.env.PORT ||
|
|
235
|
+
console.log(`\n📊 Dashboard available at: http://localhost:${process.env.PORT || 7331}${dashboardPath}\n`);
|
|
236
236
|
},
|
|
237
237
|
};
|
|
238
238
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const { buildFilteredQuery } = require('../admin-panel/core/api-extensions');
|
|
7
|
+
const { safeJsonParse } = require('../../core/utils/json');
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* @param {import('../../core/orm/database').Database} db
|
|
@@ -17,11 +18,7 @@ async function resolveExportRecords(db, modelName, req) {
|
|
|
17
18
|
let filters = body.filters ?? req.query.filters;
|
|
18
19
|
if (typeof selectAll === 'string') selectAll = selectAll === 'true';
|
|
19
20
|
if (typeof filters === 'string') {
|
|
20
|
-
|
|
21
|
-
filters = JSON.parse(filters);
|
|
22
|
-
} catch {
|
|
23
|
-
filters = undefined;
|
|
24
|
-
}
|
|
21
|
+
filters = safeJsonParse(filters, undefined);
|
|
25
22
|
}
|
|
26
23
|
|
|
27
24
|
const trashedOnly =
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const { purgeEmailLogs } = require('./purge');
|
|
7
|
+
const { safeJsonParse } = require('../../core/utils/json');
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* @param {Object} options
|
|
@@ -44,14 +45,7 @@ function createEmailApiHandlers(options) {
|
|
|
44
45
|
return res.status(400).json({ error: 'to and subject are required' });
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
if (typeof data === 'string') {
|
|
49
|
-
try {
|
|
50
|
-
parsedData = JSON.parse(data);
|
|
51
|
-
} catch {
|
|
52
|
-
parsedData = {};
|
|
53
|
-
}
|
|
54
|
-
}
|
|
48
|
+
const parsedData = safeJsonParse(data, {}) || {};
|
|
55
49
|
|
|
56
50
|
const result = await emailService.send({
|
|
57
51
|
to,
|
|
@@ -59,7 +53,7 @@ function createEmailApiHandlers(options) {
|
|
|
59
53
|
template,
|
|
60
54
|
mjml,
|
|
61
55
|
html,
|
|
62
|
-
data: parsedData
|
|
56
|
+
data: parsedData,
|
|
63
57
|
});
|
|
64
58
|
|
|
65
59
|
res.json({ ok: true, ...result });
|
|
@@ -73,17 +67,10 @@ function createEmailApiHandlers(options) {
|
|
|
73
67
|
const body = req.body || {};
|
|
74
68
|
const { template, mjml, html, data } = body;
|
|
75
69
|
|
|
76
|
-
|
|
77
|
-
if (typeof data === 'string') {
|
|
78
|
-
try {
|
|
79
|
-
parsedData = JSON.parse(data);
|
|
80
|
-
} catch {
|
|
81
|
-
parsedData = {};
|
|
82
|
-
}
|
|
83
|
-
}
|
|
70
|
+
const parsedData = safeJsonParse(data, {}) || {};
|
|
84
71
|
|
|
85
72
|
const result = await emailService.preview(
|
|
86
|
-
template ? { template, data: parsedData
|
|
73
|
+
template ? { template, data: parsedData } : { mjml, html, data: parsedData }
|
|
87
74
|
);
|
|
88
75
|
|
|
89
76
|
res.json(result);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
const { createKnexAuthTokensAdapter } = require('../../core/auth/tokens');
|
|
7
7
|
const { registerBundledAuthTemplates } = require('./bundled-templates');
|
|
8
|
+
const { stripTrailingSlash } = require('../../core/utils/url');
|
|
8
9
|
|
|
9
10
|
const DEFAULT_AUTH_EMAILS = {
|
|
10
11
|
passwordReset: {
|
|
@@ -62,7 +63,7 @@ function wireAuthEmails(authManager, emailService, registry, config, db) {
|
|
|
62
63
|
authManager.authTokens = createKnexAuthTokensAdapter(db);
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
const baseUrl = (merged.baseUrl || process.env.BASE_URL || 'http://localhost:
|
|
66
|
+
const baseUrl = stripTrailingSlash(merged.baseUrl || process.env.BASE_URL || 'http://localhost:7331');
|
|
66
67
|
const identifierField = authManager.identifierField || 'email';
|
|
67
68
|
|
|
68
69
|
function userDisplayName(user) {
|
|
@@ -153,7 +154,7 @@ function registerAuthEmailRoutes(options) {
|
|
|
153
154
|
const guest = ctx.middlewares?.guest;
|
|
154
155
|
const authMw = ctx.middlewares?.auth;
|
|
155
156
|
|
|
156
|
-
const base = (authEmailsConfig.baseUrl || process.env.BASE_URL || 'http://localhost:
|
|
157
|
+
const base = stripTrailingSlash(authEmailsConfig.baseUrl || process.env.BASE_URL || 'http://localhost:7331');
|
|
157
158
|
|
|
158
159
|
async function forgotPasswordHandler(req, res) {
|
|
159
160
|
try {
|
package/plugins/email/index.js
CHANGED
|
@@ -241,16 +241,18 @@ function emailPlugin(options = {}) {
|
|
|
241
241
|
setAppContext({ mailer });
|
|
242
242
|
if (ctx.app) {
|
|
243
243
|
ctx.app.mailer = mailer;
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
244
|
+
}
|
|
245
|
+
const target = ctx.http || ctx.app;
|
|
246
|
+
if (target && typeof target.use === 'function') {
|
|
247
|
+
target.use((req, res, next) => {
|
|
248
|
+
req.mailer = mailer;
|
|
249
|
+
next();
|
|
250
|
+
});
|
|
250
251
|
}
|
|
251
252
|
ctx.mailer = mailer;
|
|
252
253
|
|
|
253
|
-
|
|
254
|
+
const registry = ctx.serviceRegistry || ctx.services || ctx.app?.serviceRegistry;
|
|
255
|
+
if (registry) {
|
|
254
256
|
const { createMailServices } = require('../../src/services/builtins/mail');
|
|
255
257
|
const mailServices = createMailServices({
|
|
256
258
|
emailService,
|
|
@@ -259,8 +261,8 @@ function emailPlugin(options = {}) {
|
|
|
259
261
|
tableName,
|
|
260
262
|
});
|
|
261
263
|
for (const [name, def] of Object.entries(mailServices)) {
|
|
262
|
-
if (!
|
|
263
|
-
|
|
264
|
+
if (!registry.has(name)) {
|
|
265
|
+
registry.register(name, def);
|
|
264
266
|
}
|
|
265
267
|
}
|
|
266
268
|
}
|
package/plugins/health-check.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const pathMod = require('path');
|
|
6
6
|
const PKG = require(pathMod.join(__dirname, '..', 'package.json'));
|
|
7
|
+
const { ensureLeadingSlash } = require('../core/utils/url');
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* @param {Object} [options]
|
|
@@ -23,7 +24,7 @@ function healthCheckPlugin(options = {}) {
|
|
|
23
24
|
} = options;
|
|
24
25
|
|
|
25
26
|
const isEnabled = enabled !== undefined ? enabled : true;
|
|
26
|
-
const normalizedPath = routePath
|
|
27
|
+
const normalizedPath = ensureLeadingSlash(routePath);
|
|
27
28
|
|
|
28
29
|
return {
|
|
29
30
|
name: 'health-check',
|
package/plugins/index.js
CHANGED
|
@@ -23,6 +23,9 @@ module.exports = {
|
|
|
23
23
|
get ormCacheAdminPlugin() { return require('./orm-cache-admin'); },
|
|
24
24
|
get uploadPlugin() { return require('./upload').uploadPlugin; },
|
|
25
25
|
get createLocalFileProvider() { return require('./upload').createLocalFileProvider; },
|
|
26
|
+
get createR2FileProvider() { return require('./upload').createR2FileProvider; },
|
|
27
|
+
get createS3FileProvider() { return require('./upload').createS3FileProvider; },
|
|
28
|
+
get createGcsFileProvider() { return require('./upload').createGcsFileProvider; },
|
|
26
29
|
get dataExchangePlugin() { return require('./data-exchange').dataExchangePlugin; },
|
|
27
30
|
get redirectPlugin() { return require('./redirect').redirectPlugin; },
|
|
28
31
|
get rateLimitPlugin() { return require('./rate-limit').rateLimitPlugin; },
|
|
@@ -63,6 +66,8 @@ module.exports = {
|
|
|
63
66
|
get WebhookDispatcher() { return require('./webhooks').WebhookDispatcher; },
|
|
64
67
|
get lazyPlugin() { return require('./lazy').lazyPlugin; },
|
|
65
68
|
get defineLazyPlugin() { return require('./lazy').defineLazyPlugin; },
|
|
69
|
+
get rlsPlugin() { return require('./rls').rlsPlugin; },
|
|
70
|
+
get createRlsMiddleware() { return require('./rls').createRlsMiddleware; },
|
|
66
71
|
};
|
|
67
72
|
|
|
68
73
|
|
package/plugins/lazy.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
const express = require('express');
|
|
16
|
+
const { ensureArray } = require('../core/utils/object');
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Wraps a plugin definition or factory function into an on-demand lazy plugin.
|
|
@@ -43,9 +44,7 @@ function lazyPlugin(pluginFactory, lazyOptions = {}) {
|
|
|
43
44
|
(typeof pluginFactory === 'object' && pluginFactory ? pluginFactory.name : null) ||
|
|
44
45
|
'anonymous-lazy-plugin';
|
|
45
46
|
|
|
46
|
-
const rawPrefixes = lazyOptions.prefix
|
|
47
|
-
? (Array.isArray(lazyOptions.prefix) ? lazyOptions.prefix : [lazyOptions.prefix])
|
|
48
|
-
: [];
|
|
47
|
+
const rawPrefixes = ensureArray(lazyOptions.prefix);
|
|
49
48
|
// Normalize prefixes: ensure leading slash, remove trailing slash (except root '/')
|
|
50
49
|
const prefixes = rawPrefixes.map(p => {
|
|
51
50
|
let norm = String(p).trim();
|
|
@@ -54,9 +53,9 @@ function lazyPlugin(pluginFactory, lazyOptions = {}) {
|
|
|
54
53
|
return norm;
|
|
55
54
|
});
|
|
56
55
|
|
|
57
|
-
const lazyServices =
|
|
58
|
-
const lazyHelpers =
|
|
59
|
-
const lazyEvents =
|
|
56
|
+
const lazyServices = ensureArray(lazyOptions.services);
|
|
57
|
+
const lazyHelpers = ensureArray(lazyOptions.helpers);
|
|
58
|
+
const lazyEvents = ensureArray(lazyOptions.events);
|
|
60
59
|
|
|
61
60
|
function hydrate() {
|
|
62
61
|
if (isHydrated) return realPlugin;
|
package/plugins/mcp/discovery.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
|
+
const { walkDirectory } = require('../../core/utils/fs');
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Scan a directory for JS files and require them
|
|
@@ -13,27 +14,27 @@ const path = require('path');
|
|
|
13
14
|
* @returns {Array<Object>}
|
|
14
15
|
*/
|
|
15
16
|
function scanModuleDir(dirPath) {
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
const files = walkDirectory(dirPath, {
|
|
18
|
+
recursive: false,
|
|
19
|
+
extensions: ['.js', '.cjs'],
|
|
20
|
+
ignorePrefixes: ['_', '.'],
|
|
21
|
+
ignoreTests: true,
|
|
22
|
+
});
|
|
18
23
|
const items = [];
|
|
19
24
|
|
|
20
|
-
for (const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (!mod.name && !mod.uri) {
|
|
29
|
-
const baseName = path.basename(entry.name, path.extname(entry.name));
|
|
30
|
-
mod.name = baseName;
|
|
31
|
-
}
|
|
32
|
-
items.push(mod);
|
|
25
|
+
for (const fullPath of files) {
|
|
26
|
+
try {
|
|
27
|
+
const mod = require(fullPath);
|
|
28
|
+
if (mod && typeof mod === 'object') {
|
|
29
|
+
// If mod doesn't have a name, default to filename without extension
|
|
30
|
+
if (!mod.name && !mod.uri) {
|
|
31
|
+
const baseName = path.basename(fullPath, path.extname(fullPath));
|
|
32
|
+
mod.name = baseName;
|
|
33
33
|
}
|
|
34
|
-
|
|
35
|
-
console.warn(`[mcp-discovery] Failed to load module at ${fullPath}:`, err.message);
|
|
34
|
+
items.push(mod);
|
|
36
35
|
}
|
|
36
|
+
} catch (err) {
|
|
37
|
+
console.warn(`[mcp-discovery] Failed to load module at ${fullPath}:`, err.message);
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
|
package/plugins/polar/src/api.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { fetchWithTimeout } = require('../../../core/utils/fetch');
|
|
4
|
+
const { ensureLeadingSlash } = require('../../../core/utils/url');
|
|
4
5
|
|
|
5
6
|
function getPolarApiBase(sandbox) {
|
|
6
7
|
return sandbox ? 'https://sandbox-api.polar.sh/v1' : 'https://api.polar.sh/v1';
|
|
@@ -21,7 +22,7 @@ async function polarApiRequest(path, { method = 'GET', body, accessToken, sandbo
|
|
|
21
22
|
throw new Error('POLAR_ACCESS_TOKEN is not configured');
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
const normalizedPath = path
|
|
25
|
+
const normalizedPath = ensureLeadingSlash(path);
|
|
25
26
|
|
|
26
27
|
const res = await fetchWithTimeout(`${getPolarApiBase(sandbox)}${normalizedPath}`, {
|
|
27
28
|
method,
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const { safeCompare } = require('../../../core/utils/crypto');
|
|
3
|
+
const { safeCompare, hmacSha256 } = require('../../../core/utils/crypto');
|
|
5
4
|
const { syncPolarBillingForUser, applyPolarProSubscription } = require('./sync');
|
|
6
5
|
const {
|
|
7
6
|
collectPolarMetadata,
|
|
@@ -38,10 +37,10 @@ function verifyPolarWebhook(rawPayload, headers, secret) {
|
|
|
38
37
|
? Buffer.from(secret.slice(6), 'base64')
|
|
39
38
|
: secret;
|
|
40
39
|
|
|
41
|
-
const expectedBase64 =
|
|
42
|
-
const expectedHex =
|
|
43
|
-
const rawExpectedBase64 =
|
|
44
|
-
const rawExpectedHex =
|
|
40
|
+
const expectedBase64 = hmacSha256(standardToSign, cleanSecret, 'base64');
|
|
41
|
+
const expectedHex = hmacSha256(standardToSign, cleanSecret, 'hex');
|
|
42
|
+
const rawExpectedBase64 = hmacSha256(payloadStr, cleanSecret, 'base64');
|
|
43
|
+
const rawExpectedHex = hmacSha256(payloadStr, cleanSecret, 'hex');
|
|
45
44
|
|
|
46
45
|
const expectedList = [expectedBase64, expectedHex, rawExpectedBase64, rawExpectedHex];
|
|
47
46
|
|
package/plugins/queue/index.js
CHANGED
|
@@ -12,13 +12,17 @@ const {
|
|
|
12
12
|
MemoryQueueAdapter,
|
|
13
13
|
DatabaseQueueAdapter,
|
|
14
14
|
RedisQueueAdapter,
|
|
15
|
+
WaitUntilQueueAdapter,
|
|
16
|
+
CloudflareQueueAdapter,
|
|
15
17
|
} = require('../../core/queue');
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
20
|
* @param {Object} [options]
|
|
19
|
-
* @param {'memory'|'database'|'redis'|Object} [options.adapter='memory'] - Queue backend adapter or name
|
|
21
|
+
* @param {'memory'|'database'|'redis'|'wait-until'|'cloudflare'|Object} [options.adapter='memory'] - Queue backend adapter or name
|
|
20
22
|
* @param {Object} [options.db] - Knex/ORM database instance for database adapter
|
|
21
23
|
* @param {Object} [options.redisClient] - Redis client instance for redis adapter
|
|
24
|
+
* @param {Object} [options.queue] - Cloudflare Queue binding for cloudflare adapter
|
|
25
|
+
* @param {Function} [options.waitUntil] - Optional global waitUntil handler for wait-until adapter
|
|
22
26
|
* @param {string} [options.tableName='webspresso_jobs'] - Database queue table name
|
|
23
27
|
* @param {number} [options.concurrency=2] - Worker concurrency
|
|
24
28
|
* @param {number} [options.pollInterval=500] - Idle poll interval in ms
|
|
@@ -37,7 +41,7 @@ function queuePlugin(options = {}) {
|
|
|
37
41
|
return {
|
|
38
42
|
name: 'queue',
|
|
39
43
|
version: '1.0.0',
|
|
40
|
-
description: 'Background Job Queue Engine with Memory, DB, and
|
|
44
|
+
description: 'Background Job Queue Engine with Memory, DB, Redis, WaitUntil, and Cloudflare adapters',
|
|
41
45
|
|
|
42
46
|
register(ctx) {
|
|
43
47
|
const { app, db } = ctx;
|
|
@@ -46,6 +50,18 @@ function queuePlugin(options = {}) {
|
|
|
46
50
|
|
|
47
51
|
if (!adapter || adapter === 'memory') {
|
|
48
52
|
adapter = new MemoryQueueAdapter();
|
|
53
|
+
} else if (adapter === 'wait-until' || adapter === 'edge') {
|
|
54
|
+
adapter = new WaitUntilQueueAdapter({
|
|
55
|
+
waitUntil: options.waitUntil,
|
|
56
|
+
});
|
|
57
|
+
} else if (adapter === 'cloudflare' || adapter === 'cf') {
|
|
58
|
+
const cfQueue = options.queue || options.cfQueue;
|
|
59
|
+
if (!cfQueue) {
|
|
60
|
+
throw new Error('[queuePlugin] Cloudflare queue adapter requires a valid queue binding in options.queue.');
|
|
61
|
+
}
|
|
62
|
+
adapter = new CloudflareQueueAdapter({
|
|
63
|
+
queue: cfQueue,
|
|
64
|
+
});
|
|
49
65
|
} else if (adapter === 'database' || adapter === 'db') {
|
|
50
66
|
const knexInstance = options.db?.knex || db?.knex || options.knex;
|
|
51
67
|
if (!knexInstance) {
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* @module plugins/rate-limit
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
const { ensureArray } = require('../../core/utils/object');
|
|
7
|
+
|
|
6
8
|
const PLUGIN_ONLY_KEYS = new Set(['global', 'globalOverrides', 'globalSkipPaths']);
|
|
7
9
|
|
|
8
10
|
const DEFAULT_BASE = {
|
|
@@ -95,7 +97,7 @@ function combineSkip(userSkip, builtin) {
|
|
|
95
97
|
* @returns {import('express').RequestHandler}
|
|
96
98
|
*/
|
|
97
99
|
function createDefaultGlobalSkip(extraPathPrefixes = []) {
|
|
98
|
-
const extras = (
|
|
100
|
+
const extras = ensureArray(extraPathPrefixes).filter(Boolean);
|
|
99
101
|
/** @type {string[]} */
|
|
100
102
|
const prefixes = [
|
|
101
103
|
'/__webspresso/client-runtime',
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
7
|
+
const { stripTrailingSlash, ensureTrailingSlash } = require('../../core/utils/url');
|
|
8
|
+
const { ensureArray } = require('../../core/utils/object');
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* @param {string} to
|
|
@@ -32,10 +34,10 @@ function normalizePathForTrailingSlash(mode, path) {
|
|
|
32
34
|
if (!path || mode === false || mode == null) return path;
|
|
33
35
|
if (path === '/') return path;
|
|
34
36
|
if (mode === 'strip') {
|
|
35
|
-
return
|
|
37
|
+
return stripTrailingSlash(path, { preserveRoot: true });
|
|
36
38
|
}
|
|
37
39
|
if (mode === 'add') {
|
|
38
|
-
return path
|
|
40
|
+
return ensureTrailingSlash(path);
|
|
39
41
|
}
|
|
40
42
|
return path;
|
|
41
43
|
}
|
|
@@ -119,8 +121,8 @@ function pathMatches(compiled, pathForMatch, rawPath) {
|
|
|
119
121
|
if (compiled.kind === 'string') {
|
|
120
122
|
if (pathForMatch === compiled.fromNorm) return true;
|
|
121
123
|
if (!compiled.trailing) {
|
|
122
|
-
const a = rawPath
|
|
123
|
-
const b = compiled.fromRaw
|
|
124
|
+
const a = stripTrailingSlash(rawPath, { preserveRoot: true });
|
|
125
|
+
const b = stripTrailingSlash(compiled.fromRaw, { preserveRoot: true });
|
|
124
126
|
if (a === b) return true;
|
|
125
127
|
}
|
|
126
128
|
return false;
|
|
@@ -173,7 +175,7 @@ function redirectPlugin(options = {}) {
|
|
|
173
175
|
const stringRulesMap = new Map();
|
|
174
176
|
let hasRegexRules = false;
|
|
175
177
|
|
|
176
|
-
const ruleList =
|
|
178
|
+
const ruleList = ensureArray(rules);
|
|
177
179
|
for (const rule of ruleList) {
|
|
178
180
|
const c = compileRule(rule, pluginOpts);
|
|
179
181
|
if (!c) continue;
|
|
@@ -181,7 +183,7 @@ function redirectPlugin(options = {}) {
|
|
|
181
183
|
if (c.kind === 'string') {
|
|
182
184
|
const keys = [c.fromNorm];
|
|
183
185
|
if (!c.trailing) {
|
|
184
|
-
const alt = c.fromRaw
|
|
186
|
+
const alt = stripTrailingSlash(c.fromRaw, { preserveRoot: true });
|
|
185
187
|
if (!keys.includes(alt)) keys.push(alt);
|
|
186
188
|
}
|
|
187
189
|
for (const k of keys) {
|
|
@@ -205,7 +207,7 @@ function redirectPlugin(options = {}) {
|
|
|
205
207
|
|
|
206
208
|
// Fast O(1) path when all configured rules are string based
|
|
207
209
|
if (!hasRegexRules) {
|
|
208
|
-
const candidates = stringRulesMap.get(pathForMatch) || (!pluginOpts.trailingSlash ? stringRulesMap.get(rawPath
|
|
210
|
+
const candidates = stringRulesMap.get(pathForMatch) || (!pluginOpts.trailingSlash ? stringRulesMap.get(stripTrailingSlash(rawPath, { preserveRoot: true })) : null);
|
|
209
211
|
if (candidates) {
|
|
210
212
|
for (const c of candidates) {
|
|
211
213
|
if (!methodMatches(c, method)) continue;
|
|
@@ -251,8 +253,13 @@ function redirectPlugin(options = {}) {
|
|
|
251
253
|
description: 'Configurable HTTP redirects before file-based routes',
|
|
252
254
|
|
|
253
255
|
register(ctx) {
|
|
254
|
-
if (compiled.length === 0)
|
|
255
|
-
|
|
256
|
+
if (compiled.length === 0) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const target = ctx.http || ctx.app;
|
|
260
|
+
if (target && typeof target.use === 'function') {
|
|
261
|
+
target.use(redirectMiddleware);
|
|
262
|
+
}
|
|
256
263
|
},
|
|
257
264
|
};
|
|
258
265
|
}
|