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
package/.agents/FLAGS.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Feature Flags & Canary Traffic Splitter (`core/flags`)
|
|
2
|
+
|
|
3
|
+
Webspresso features an enterprise-grade, zero-dependency **Feature Flag & Canary Traffic Splitting Engine** in `core/flags/`. It enables gradual rollouts, multivariate A/B testing, user/role/domain targeting, and runtime overrides across Nunjucks SSR templates, API endpoints (`defineApi`), service layers, and Edge/Hono adapters.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🎯 Key Capabilities
|
|
8
|
+
|
|
9
|
+
1. **Sub-Microsecond Deterministic Bucketing**:
|
|
10
|
+
- FNV-1a 32-bit hashing algorithm (`getBucket`) maps user identifiers to deterministic integer buckets `0 .. 99` across distributed nodes without network calls.
|
|
11
|
+
2. **Flexible Targeting Rules**:
|
|
12
|
+
- **Boolean Toggles**: `enabled: true | false`.
|
|
13
|
+
- **Canary Percentage Rollout**: `rollout: 10` (10% of traffic, consistent per user).
|
|
14
|
+
- **Targeted Whitelists**: `users: ['user_123', 'admin@corp.com']`.
|
|
15
|
+
- **Role-Based Targeting**: `roles: ['admin', 'beta_tester']`.
|
|
16
|
+
- **Email Domain Targeting**: `emailDomains: ['acme.com']`.
|
|
17
|
+
- **Multivariate A/B Variants**: `variants: [{ name: 'blue_button', weight: 50 }, { name: 'green_button', weight: 50 }]`.
|
|
18
|
+
- **Custom Predicates**: `condition: (ctx) => ctx.plan === 'enterprise'`.
|
|
19
|
+
3. **Multi-Layer Integration**:
|
|
20
|
+
- **Express / Hono Middleware**: Automatically injects `req.flags`, `req.hasFeature()`, and `req.getVariant()`.
|
|
21
|
+
- **Nunjucks SSR Templates**: Access via `{{ fsy.hasFeature('new_ui') }}` and `{{ fsy.flag('theme') }}`.
|
|
22
|
+
- **Declarative API Route Guard**: `defineApi({ flag: 'beta_feature' })` returns HTTP 404 (or configured status) if disabled.
|
|
23
|
+
- **Environment Variable Overrides**: Instant runtime overrides via `FEATURE_<FLAG_NAME>=true|false`.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 🚀 Quick Start
|
|
28
|
+
|
|
29
|
+
### 1. Application Configuration
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
const { createApp } = require('webspresso');
|
|
33
|
+
|
|
34
|
+
const app = createApp({
|
|
35
|
+
flags: {
|
|
36
|
+
// 1. Simple toggle
|
|
37
|
+
dark_mode: true,
|
|
38
|
+
|
|
39
|
+
// 2. 20% Canary rollout
|
|
40
|
+
new_checkout: {
|
|
41
|
+
rollout: 20,
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
// 3. User & Role targeting
|
|
45
|
+
ai_assistant: {
|
|
46
|
+
roles: ['pro', 'admin'],
|
|
47
|
+
users: ['vip_client_1'],
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
// 4. Multivariate A/B Test
|
|
51
|
+
pricing_page_test: {
|
|
52
|
+
variants: [
|
|
53
|
+
{ name: 'control', weight: 50 },
|
|
54
|
+
{ name: 'discount_banner', weight: 50 },
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 2. Nunjucks SSR Templates (`pages/*.njk`)
|
|
62
|
+
|
|
63
|
+
```nunjucks
|
|
64
|
+
{% if fsy.hasFeature('new_checkout') %}
|
|
65
|
+
{% include "components/new-checkout.njk" %}
|
|
66
|
+
{% else %}
|
|
67
|
+
{% include "components/legacy-checkout.njk" %}
|
|
68
|
+
{% endif %}
|
|
69
|
+
|
|
70
|
+
{% if fsy.flag('pricing_page_test') == 'discount_banner' %}
|
|
71
|
+
<div class="banner">Special 20% Discount Applied!</div>
|
|
72
|
+
{% endif %}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 3. File-Based API Endpoints (`pages/api/...`)
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
const { defineApi } = require('webspresso');
|
|
79
|
+
|
|
80
|
+
module.exports = defineApi({
|
|
81
|
+
// Automatically blocks request with HTTP 404 if flag is disabled
|
|
82
|
+
flag: 'ai_assistant',
|
|
83
|
+
handler: async (req, res, ctx) => {
|
|
84
|
+
// Also accessible inside ctx or req
|
|
85
|
+
const isVip = ctx.hasFeature('vip_access');
|
|
86
|
+
return res.json({ success: true, isVip });
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 4. Route Guard Middleware
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
const { createFeatureFlagManager } = require('webspresso');
|
|
95
|
+
|
|
96
|
+
const flags = createFeatureFlagManager({
|
|
97
|
+
flags: { secret_admin: { roles: ['admin'] } },
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Use as standalone route middleware
|
|
101
|
+
app.get('/admin/secret', flags.guard('secret_admin', { statusCode: 403 }), (req, res) => {
|
|
102
|
+
res.send('Welcome, Admin!');
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## ⚙️ Environment Overrides
|
|
109
|
+
|
|
110
|
+
Flags can be overridden at runtime without code deployment or restart:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# Enable flag
|
|
114
|
+
FEATURE_NEW_CHECKOUT=true npm start
|
|
115
|
+
|
|
116
|
+
# Disable flag
|
|
117
|
+
FEATURE_NEW_CHECKOUT=false npm start
|
|
118
|
+
```
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Cloud & Kubernetes Health & Readiness Probes (`core/health`)
|
|
2
|
+
|
|
3
|
+
Webspresso includes a zero-dependency, Docker & Kubernetes-native health inspection system. It provides standard liveness (`/healthz`), readiness (`/readyz`), and combined diagnostics endpoints with automatic database pings, background worker queue checks, event loop delay metrics, and instant graceful shutdown draining integration.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🎯 Key Capabilities
|
|
8
|
+
|
|
9
|
+
1. **Kubernetes Liveness Probe (`/healthz`)**:
|
|
10
|
+
- Confirms process vitality and responsiveness.
|
|
11
|
+
- Gathers memory footprint (`rss`, `heapUsed`, `heapTotal` formatted and raw bytes), process uptime, PID, platform, and event loop lag statistics (`meanMs`, `maxMs`, `p99Ms`).
|
|
12
|
+
- If event loop delay exceeds threshold (`eventLoopThresholdMs`), flags status as `degraded`.
|
|
13
|
+
|
|
14
|
+
2. **Kubernetes Readiness Probe (`/readyz`)**:
|
|
15
|
+
- Confirms the container is fully prepared to handle live client traffic.
|
|
16
|
+
- **Database Connectivity**: Pings active Knex instance (`SELECT 1`) with timeout protection.
|
|
17
|
+
- **Queue Readiness**: Probes worker queue instance status.
|
|
18
|
+
- **Custom Probes**: Executes user-registered async checks concurrently (`checks: { redis, stripe }`).
|
|
19
|
+
- Returns `HTTP 200` (`{ ready: true, status: 'ready' }`) when all components are healthy.
|
|
20
|
+
- Returns `HTTP 503` (`{ ready: false, status: 'unhealthy', checks: ... }`) if any component fails.
|
|
21
|
+
|
|
22
|
+
3. **Graceful Draining Integration (`ShutdownManager`)**:
|
|
23
|
+
- As soon as the server enters shutdown draining (`app.close()` or `SIGTERM`/`SIGINT`), `/readyz` immediately returns `HTTP 503` (`{ ready: false, status: 'draining' }`).
|
|
24
|
+
- Cloud load balancers (AWS ALB, K8s Ingress, Google Cloud, Cloudflare) immediately stop routing new traffic to this instance, while existing active requests drain cleanly without dropping a single connection.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 🚀 Configuration Examples
|
|
29
|
+
|
|
30
|
+
### 1. Simple Enablement
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
const { createApp } = require('webspresso');
|
|
34
|
+
|
|
35
|
+
const app = createApp({
|
|
36
|
+
pagesDir: 'pages',
|
|
37
|
+
// Enables /healthz (liveness), /readyz (readiness), and /health (combined)
|
|
38
|
+
healthCheck: true,
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Shorthand custom path:
|
|
43
|
+
```javascript
|
|
44
|
+
const app = createApp({
|
|
45
|
+
pagesDir: 'pages',
|
|
46
|
+
healthCheck: '/api/status', // Mounts combined status on /api/status
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 2. Full Production Configuration
|
|
51
|
+
|
|
52
|
+
```javascript
|
|
53
|
+
const app = createApp({
|
|
54
|
+
pagesDir: 'pages',
|
|
55
|
+
db: myDatabaseInstance,
|
|
56
|
+
queue: myQueueInstance,
|
|
57
|
+
healthCheck: {
|
|
58
|
+
enabled: true,
|
|
59
|
+
path: '/healthz', // Liveness probe URL
|
|
60
|
+
readyPath: '/readyz', // Readiness probe URL
|
|
61
|
+
combinedPath: '/health', // Diagnostic summary URL
|
|
62
|
+
db: true, // Automatically probe database (SELECT 1)
|
|
63
|
+
queue: true, // Automatically probe worker queue
|
|
64
|
+
eventLoopLag: true, // Monitor event loop delay
|
|
65
|
+
eventLoopThresholdMs: 300, // Lag threshold in ms
|
|
66
|
+
timeoutMs: 2500, // Probe timeout before marking down
|
|
67
|
+
checks: { // Custom dependency probes
|
|
68
|
+
redis: async () => redisClient.ping(),
|
|
69
|
+
stripe: async () => checkStripeHealth(),
|
|
70
|
+
},
|
|
71
|
+
headers: {
|
|
72
|
+
'Cache-Control': 'no-store, no-cache, must-revalidate',
|
|
73
|
+
'X-Content-Type-Options': 'nosniff',
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## ☸️ Kubernetes Pod Spec Example
|
|
82
|
+
|
|
83
|
+
```yaml
|
|
84
|
+
livenessProbe:
|
|
85
|
+
httpGet:
|
|
86
|
+
path: /healthz
|
|
87
|
+
port: 3000
|
|
88
|
+
initialDelaySeconds: 5
|
|
89
|
+
periodSeconds: 10
|
|
90
|
+
timeoutSeconds: 3
|
|
91
|
+
failureThreshold: 3
|
|
92
|
+
|
|
93
|
+
readinessProbe:
|
|
94
|
+
httpGet:
|
|
95
|
+
path: /readyz
|
|
96
|
+
port: 3000
|
|
97
|
+
initialDelaySeconds: 2
|
|
98
|
+
periodSeconds: 5
|
|
99
|
+
timeoutSeconds: 2
|
|
100
|
+
failureThreshold: 2
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 🔍 Programmatic Inspection
|
|
106
|
+
|
|
107
|
+
Access health state directly in code or CLI scripts:
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
// Get complete diagnostic payload
|
|
111
|
+
const status = await app.getHealthStatus();
|
|
112
|
+
console.log('Liveness:', status.liveness);
|
|
113
|
+
console.log('Readiness:', status.readiness);
|
|
114
|
+
|
|
115
|
+
// Or via healthManager
|
|
116
|
+
const liveness = await app.healthManager.getLiveness();
|
|
117
|
+
const readiness = await app.healthManager.getReadiness();
|
|
118
|
+
```
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Configurable Payload Size Limits & DoS Protection (`core/limits`)
|
|
2
|
+
|
|
3
|
+
Webspresso provides a zero-dependency, configurable request body size limiter and Denial of Service (DoS) protection layer. It safeguards Node.js memory against payload exhaustion attacks while supporting per-route overrides, streaming bypass, and native raw body capture for HMAC webhook verification.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🎯 Key Capabilities
|
|
8
|
+
|
|
9
|
+
1. **Multi-Parser Configuration**:
|
|
10
|
+
- `json`: Size limit for `application/json` (default: `'1mb'`).
|
|
11
|
+
- `urlencoded`: Size limit for `application/x-www-form-urlencoded` (default: `'1mb'`).
|
|
12
|
+
- `parameterLimit`: Maximum number of URL-encoded form parameters (default: `1000`).
|
|
13
|
+
- `raw`: Size limit for `application/octet-stream` (default: `false` / disabled).
|
|
14
|
+
- `text`: Size limit for `text/plain` (default: `false` / disabled).
|
|
15
|
+
2. **Standard Exception Mapping**:
|
|
16
|
+
- Limit exceeded -> Throws `PayloadTooLargeError` (HTTP 413) with `{ limit, length, expected }` details and `code: 'PAYLOAD_TOO_LARGE'`.
|
|
17
|
+
- Parameter exhaustion -> Throws `PayloadTooLargeError` (HTTP 413) with `code: 'PARAMETERS_TOO_MANY'`.
|
|
18
|
+
- Malformed JSON -> Throws `BadRequestError` (HTTP 400) with `code: 'INVALID_JSON_PAYLOAD'`.
|
|
19
|
+
3. **Webhook & HMAC Signature Verification**:
|
|
20
|
+
- `captureRawBody: true` stores incoming unparsed `Buffer` on `req.rawBody` for zero-overhead cryptographic signatures (Polar, Stripe, LemonSqueezy, GitHub).
|
|
21
|
+
4. **Route Overrides & Streaming Bypass**:
|
|
22
|
+
- `limits.routes`: Generous limits for specific paths (e.g. `/api/upload` -> 25MB) without loosening server-wide protection.
|
|
23
|
+
- `limits.exclude`: Completely skips global body parsers for streaming and custom multipart endpoints.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 🚀 Configuration Examples
|
|
28
|
+
|
|
29
|
+
### 1. Global Configuration
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
const { createApp } = require('webspresso');
|
|
33
|
+
|
|
34
|
+
const app = createApp({
|
|
35
|
+
pagesDir: 'pages',
|
|
36
|
+
limits: {
|
|
37
|
+
json: '2mb', // 2 Megabytes for JSON
|
|
38
|
+
urlencoded: '1mb', // 1 Megabyte for Forms
|
|
39
|
+
parameterLimit: 2000, // Up to 2000 parameters
|
|
40
|
+
raw: '10mb', // Enable raw buffer parsing for binary data
|
|
41
|
+
captureRawBody: true, // Automatically populate req.rawBody
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Shorthand (applies to both JSON and URL-encoded):
|
|
47
|
+
```javascript
|
|
48
|
+
const app = createApp({
|
|
49
|
+
pagesDir: 'pages',
|
|
50
|
+
limits: '5mb', // Both json and urlencoded set to 5 MB
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 2. Route Overrides & Exclusions
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
const app = createApp({
|
|
58
|
+
pagesDir: 'pages',
|
|
59
|
+
limits: {
|
|
60
|
+
json: '500kb', // Tight global limit for standard APIs
|
|
61
|
+
routes: {
|
|
62
|
+
'/api/import/json': { json: '25mb' }, // Relaxed limit for batch import
|
|
63
|
+
'/api/webhooks/*': { raw: '5mb', captureRawBody: true },
|
|
64
|
+
},
|
|
65
|
+
exclude: [
|
|
66
|
+
'/api/stream/upload', // Skip global body parser entirely for socket streaming
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 3. Webhook Signature Verification with `req.rawBody`
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
// pages/api/webhooks/polar.post.js
|
|
76
|
+
const crypto = require('crypto');
|
|
77
|
+
const { defineApi, UnauthorizedError } = require('webspresso');
|
|
78
|
+
|
|
79
|
+
module.exports = defineApi({
|
|
80
|
+
handler: async (req, res) => {
|
|
81
|
+
const signature = req.headers['webhook-signature'];
|
|
82
|
+
const expected = crypto
|
|
83
|
+
.createHmac('sha256', process.env.POLAR_WEBHOOK_SECRET)
|
|
84
|
+
.update(req.rawBody) // Native Buffer captured via captureRawBody
|
|
85
|
+
.digest('hex');
|
|
86
|
+
|
|
87
|
+
if (signature !== expected) {
|
|
88
|
+
throw new UnauthorizedError('Invalid signature');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return res.json({ received: true });
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## 🛡️ Standalone Middleware Usage
|
|
99
|
+
|
|
100
|
+
For manual route mounting or custom Express routers:
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
const { createLimitsMiddleware } = require('webspresso');
|
|
104
|
+
|
|
105
|
+
// Mount on custom router or endpoint
|
|
106
|
+
app.post('/custom-endpoint', createLimitsMiddleware({ json: '50mb' }), (req, res) => {
|
|
107
|
+
res.json({ size: req.body.length });
|
|
108
|
+
});
|
|
109
|
+
```
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Structured Cloud Logging & Distributed Tracing Guide
|
|
2
|
+
|
|
3
|
+
Webspresso features an enterprise, zero-dependency Structured Cloud Logger (`core/logger`) designed specifically for serverless and containerized cloud platforms (Google Cloud Run, AWS App Runner, ECS Fargate, Lambda, Kubernetes).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🎯 Key Capabilities
|
|
8
|
+
|
|
9
|
+
1. **Native Cloud Platform Detection & Formatting**:
|
|
10
|
+
- **Google Cloud Platform (`format: 'gcp'`)**: Automatically emits JSON payloads with Google Cloud Logging `severity` levels (`DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`), `logging.googleapis.com/trace`, `logging.googleapis.com/spanId`, and RFC `httpRequest` metadata.
|
|
11
|
+
- **Amazon Web Services (`format: 'aws'`)**: Formats logs with `level`, `requestId`, `xrayTraceId`, and `http` metrics compatible with AWS CloudWatch and AWS X-Ray.
|
|
12
|
+
- **Standard JSON (`format: 'json'`)**: Compact, structured single-line JSON stream for Docker, Kubernetes, and Datadog.
|
|
13
|
+
- **Development Pretty (`format: 'pretty'`)**: Fast, ANSI color-coded console logs for local developer happiness.
|
|
14
|
+
2. **Distributed Tracing Context Propagation**:
|
|
15
|
+
- Parses incoming request trace headers automatically:
|
|
16
|
+
- `x-cloud-trace-context` (Google Cloud Trace)
|
|
17
|
+
- `x-amzn-trace-id` (AWS X-Ray)
|
|
18
|
+
- `traceparent` (W3C standard)
|
|
19
|
+
- `x-request-id` / `x-correlation-id`
|
|
20
|
+
- Injects trace ID and span ID into every log statement emitted during the request lifecycle.
|
|
21
|
+
3. **Automatic Child Logger on Requests (`req.logger`)**:
|
|
22
|
+
- In API route handlers and SSR page loaders, `req.logger` is pre-bound to the request's trace ID and contextual metadata.
|
|
23
|
+
- Automatically logs HTTP access on request finish (`res.on('finish')`) with latency, status code, remote IP, and user-agent.
|
|
24
|
+
4. **Deep Sensitive Key Redaction**:
|
|
25
|
+
- Recursively masks sensitive fields (`password`, `token`, `secret`, `authorization`, `cookie`, `apiKey`) to prevent accidental credential leakage in cloud log aggregation systems.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 🚀 Usage in Application Code
|
|
30
|
+
|
|
31
|
+
### In API Route Handlers
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
// pages/api/orders.post.js
|
|
35
|
+
const { defineApi } = require('webspresso');
|
|
36
|
+
|
|
37
|
+
module.exports = defineApi({
|
|
38
|
+
schema: ({ z }) => ({
|
|
39
|
+
body: z.object({ amount: z.number().positive(), customerId: z.string() }),
|
|
40
|
+
}),
|
|
41
|
+
handler: async (req, res) => {
|
|
42
|
+
// req.logger is automatically bound to the request's trace ID
|
|
43
|
+
req.logger.info('Processing order checkout', {
|
|
44
|
+
customerId: req.input.body.customerId,
|
|
45
|
+
amount: req.input.body.amount,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const order = await req.service('order.create', req.input.body);
|
|
50
|
+
req.logger.info('Order processed successfully', { orderId: order.id });
|
|
51
|
+
return res.status(201).json(order);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
req.logger.error('Failed to process order', { error: err.message });
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Standalone Logger & Child Context
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
const { createLogger } = require('webspresso');
|
|
64
|
+
|
|
65
|
+
const logger = createLogger({
|
|
66
|
+
format: 'auto', // 'gcp' | 'aws' | 'json' | 'pretty'
|
|
67
|
+
level: 'info',
|
|
68
|
+
defaultMeta: { service: 'payment-worker' },
|
|
69
|
+
project: 'my-gcp-project', // optional GCP project ID
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
logger.info('Worker started');
|
|
73
|
+
|
|
74
|
+
// Create contextual child logger
|
|
75
|
+
const jobLogger = logger.child({ jobId: 'job_42' });
|
|
76
|
+
jobLogger.info('Task execution started');
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Configuration in `createApp`
|
|
80
|
+
|
|
81
|
+
```javascript
|
|
82
|
+
const { createApp } = require('webspresso');
|
|
83
|
+
|
|
84
|
+
const app = createApp({
|
|
85
|
+
pagesDir: './pages',
|
|
86
|
+
logger: {
|
|
87
|
+
format: 'auto', // Auto-detected from K_SERVICE, AWS_EXECUTION_ENV, or NODE_ENV
|
|
88
|
+
level: 'info',
|
|
89
|
+
exclude: ['/healthz', '/readyz', '/favicon.ico'],
|
|
90
|
+
redact: ['password', 'secret', 'token'],
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
```
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Cloud Metrics & Distributed Tracing Guide
|
|
2
|
+
|
|
3
|
+
Webspresso provides an enterprise-grade, zero-dependency Cloud Metrics & Distributed Tracing engine (`core/metrics`) delivering Prometheus exposition format (`/metrics`), JSON telemetry (`/metrics.json`), HTTP latency histograms, and W3C/GCP/AWS distributed tracing header propagation.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🎯 Key Capabilities
|
|
8
|
+
|
|
9
|
+
1. **Prometheus-Compatible Exposition Format**:
|
|
10
|
+
- Zero external dependencies (no `prom-client` or OpenTelemetry npm packages required).
|
|
11
|
+
- Generates compliant `text/plain; version=0.0.4; charset=utf-8` output directly consumable by Prometheus, Grafana Agent, Google Cloud Managed Service for Prometheus, AWS CloudWatch Container Insights, and Datadog.
|
|
12
|
+
- Dual-format exposition: `/metrics` for Prometheus scraping and `/metrics.json` (or `Accept: application/json`) for programmatic inspection or lightweight dashboards.
|
|
13
|
+
2. **Distributed Tracing & W3C Propagation**:
|
|
14
|
+
- Parses incoming tracing headers automatically (`traceparent`, `x-cloud-trace-context`, `x-amzn-trace-id`, `x-request-id`).
|
|
15
|
+
- Automatically injects `x-trace-id` and W3C `traceparent` (`00-${traceId}-${spanId}-01`) onto outgoing HTTP response headers.
|
|
16
|
+
- Attaches `req.traceId` and `req.spanId` to the incoming request object for seamless correlation with `req.logger`.
|
|
17
|
+
3. **Core HTTP Request Telemetry**:
|
|
18
|
+
- `webspresso_http_requests_total{method, route, status}`: Counter for all processed HTTP requests.
|
|
19
|
+
- `webspresso_http_request_duration_seconds{method, route}`: Latency histogram with cumulative buckets, `_sum`, and `_count`.
|
|
20
|
+
- `webspresso_http_active_requests`: Gauge tracking in-flight concurrent requests.
|
|
21
|
+
- **Noise-free scraping**: Internal scrape calls to `/metrics`, `/metrics.json`, and `/healthz` are excluded from HTTP latency counters to prevent self-polluting metrics graphs.
|
|
22
|
+
4. **Automated Node.js & Framework Collectors**:
|
|
23
|
+
- **Process & Memory**: `process_uptime_seconds`, `nodejs_heap_size_used_bytes`, `nodejs_heap_size_total_bytes`, `process_resident_memory_bytes`, `nodejs_external_memory_bytes`.
|
|
24
|
+
- **CPU**: `process_cpu_user_seconds_total`, `process_cpu_system_seconds_total`.
|
|
25
|
+
- **Event Loop Lag**: `nodejs_eventloop_lag_seconds{quantile="0.50|0.99|max"}` via native `perf_hooks.monitorEventLoopDelay`.
|
|
26
|
+
- **Database Connection Pool**: `webspresso_db_pool_size`, `webspresso_db_pool_used`, `webspresso_db_pool_available` (Knex pool).
|
|
27
|
+
- **In-Memory Cache**: `webspresso_cache_entries`, `webspresso_cache_bytes` (LRU cache).
|
|
28
|
+
- **Circuit Breakers**: `webspresso_circuit_breaker_state{name}` (0=closed, 1=half-open, 2=open), `webspresso_circuit_breaker_failures_total{name}`.
|
|
29
|
+
5. **Security & Gating**:
|
|
30
|
+
- Protect metrics endpoints with bearer token authentication (`auth: 'secret-token'`).
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 🚀 Configuration & Enabling Metrics
|
|
35
|
+
|
|
36
|
+
### 1. Enabling via `createApp`
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
const { createApp } = require('webspresso');
|
|
40
|
+
|
|
41
|
+
const { app } = createApp({
|
|
42
|
+
pagesDir: './pages',
|
|
43
|
+
metrics: {
|
|
44
|
+
enabled: true, // default: true if metrics option is passed
|
|
45
|
+
path: '/metrics', // default: '/metrics'
|
|
46
|
+
collectDefault: true, // default: true (system, memory, pool, cache, lag)
|
|
47
|
+
traceHeader: true, // default: true (x-trace-id, traceparent)
|
|
48
|
+
auth: process.env.METRICS_TOKEN || null, // Optional Bearer token protection
|
|
49
|
+
buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Or enable with shorthand:
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
createApp({
|
|
58
|
+
metrics: true, // mounts /metrics and /metrics.json with defaults
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 📊 Custom Application Metrics
|
|
65
|
+
|
|
66
|
+
Application routes, services, and plugins can register and record custom domain metrics via `app.metrics` or `req.metrics`:
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
// Register a custom counter
|
|
70
|
+
const orderCounter = app.metrics.counter(
|
|
71
|
+
'shop_orders_placed_total',
|
|
72
|
+
'Total number of completed customer orders'
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// Register a custom gauge
|
|
76
|
+
const activeUsersGauge = app.metrics.gauge(
|
|
77
|
+
'shop_active_cart_sessions',
|
|
78
|
+
'Number of carts currently active'
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// In an API handler
|
|
82
|
+
// pages/api/checkout.post.js
|
|
83
|
+
module.exports = defineApi({
|
|
84
|
+
handler: async (req, res) => {
|
|
85
|
+
const order = await req.service('order.create', req.body);
|
|
86
|
+
|
|
87
|
+
// Increment domain metric
|
|
88
|
+
orderCounter.inc({ plan: order.plan, currency: order.currency }, 1);
|
|
89
|
+
|
|
90
|
+
return res.status(201).json(order);
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 🛡️ Standalone Registry Usage
|
|
98
|
+
|
|
99
|
+
```javascript
|
|
100
|
+
const { createMetricsRegistry } = require('webspresso');
|
|
101
|
+
|
|
102
|
+
const registry = createMetricsRegistry();
|
|
103
|
+
|
|
104
|
+
const counter = registry.counter('jobs_processed_total', 'Jobs processed by worker');
|
|
105
|
+
counter.inc({ queue: 'email', status: 'success' }, 1);
|
|
106
|
+
|
|
107
|
+
const histogram = registry.histogram('job_execution_duration_seconds', 'Job execution duration', [0.1, 0.5, 1, 5]);
|
|
108
|
+
histogram.observe({ queue: 'email' }, 0.24);
|
|
109
|
+
|
|
110
|
+
// Format output
|
|
111
|
+
console.log(registry.formatPrometheus());
|
|
112
|
+
console.log(JSON.stringify(registry.formatJson(), null, 2));
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 📈 Prometheus Scrape Configuration (`prometheus.yml`)
|
|
118
|
+
|
|
119
|
+
```yaml
|
|
120
|
+
scrape_configs:
|
|
121
|
+
- job_name: 'webspresso-app'
|
|
122
|
+
scrape_interval: 15s
|
|
123
|
+
metrics_path: '/metrics'
|
|
124
|
+
bearer_token: 'YOUR_METRICS_TOKEN' # if auth is enabled
|
|
125
|
+
static_configs:
|
|
126
|
+
- targets: ['localhost:3000']
|
|
127
|
+
```
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Cloud Migration Jobs & Zero-Downtime Database Deploy Guide
|
|
2
|
+
|
|
3
|
+
Webspresso provides an enterprise, zero-downtime database migration architecture designed specifically for Google Cloud Run, AWS ECS Fargate, AWS App Runner, and Kubernetes.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🎯 The Cloud Migration Challenge
|
|
8
|
+
|
|
9
|
+
In containerized, autoscaling cloud platforms (such as Google Cloud Run or AWS ECS), running migrations inside the web application container startup command (`CMD ["node", "server.js"]`) causes severe production risks:
|
|
10
|
+
|
|
11
|
+
1. **Concurrent Migration Race Conditions**: If a new revision autoscales to multiple container instances, multiple containers attempt to run migrations simultaneously, causing deadlock or corrupt schema states.
|
|
12
|
+
2. **Crash Loops & Downtime**: If a migration script fails, web containers fail health checks and crash-loop, but HTTP traffic might already hit partially-migrated schemas.
|
|
13
|
+
3. **Stale Lock Hangs**: If a container is forcefully terminated by Cloud Run or ECS during migration, Knex's `knex_migrations_lock` table is left locked (`Migration table is already locked`), breaking all subsequent CI/CD deployments.
|
|
14
|
+
|
|
15
|
+
### The Webspresso Solution: Dedicated Pre-Deploy Cloud Migration Jobs
|
|
16
|
+
|
|
17
|
+
Webspresso solves this by separating database schema migrations into a single, isolated pre-deploy job that runs to completion before new web service revisions receive traffic:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
[ Git Push / CI/CD ]
|
|
21
|
+
│
|
|
22
|
+
▼
|
|
23
|
+
[ Build Container Image ]
|
|
24
|
+
│
|
|
25
|
+
▼
|
|
26
|
+
[ Run Cloud Migration Job ] ──── (Fails? Stop deployment, keep old web version running)
|
|
27
|
+
│ (Success)
|
|
28
|
+
▼
|
|
29
|
+
[ Deploy Web Service Revision ] ─── (Safely route 100% traffic to new code)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 🚀 CLI Commands
|
|
35
|
+
|
|
36
|
+
### 1. Scaffold Migration Jobs (`add:migration-job`)
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# For Google Cloud Run (cloudrun-job.yaml + deploy-cloudrun-migration.sh)
|
|
40
|
+
npx webspresso add:migration-job --platform cloudrun --app my-app --cloudsql "project:region:instance"
|
|
41
|
+
|
|
42
|
+
# For AWS ECS Fargate & App Runner (ecs-migration-task.json + deploy-aws-migration.sh)
|
|
43
|
+
npx webspresso add:migration-job --platform aws --app my-app --region us-east-1
|
|
44
|
+
|
|
45
|
+
# For Kubernetes / Helm (k8s/migration-job.yaml with pre-upgrade hooks)
|
|
46
|
+
npx webspresso add:migration-job --platform k8s --app my-app
|
|
47
|
+
|
|
48
|
+
# For all platforms
|
|
49
|
+
npx webspresso add:migration-job --platform all
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## ⚡ Cloud-Safe Migration Options (`db:migrate`)
|
|
55
|
+
|
|
56
|
+
Webspresso's `db:migrate` command features cloud resilience flags powered by `core/orm/migrate-cloud.js`:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Cloud-Safe mode: exponential backoff connection retries + automatic stale lock auto-healing
|
|
60
|
+
npx webspresso db:migrate --safe --env production
|
|
61
|
+
|
|
62
|
+
# Dry-run: preview pending migrations without altering the database
|
|
63
|
+
npx webspresso db:migrate --dry-run --env production
|
|
64
|
+
|
|
65
|
+
# Auto-heal hanging locks left by aborted serverless jobs
|
|
66
|
+
npx webspresso db:migrate --unlock-if-stale
|
|
67
|
+
|
|
68
|
+
# Force unlock migration table immediately
|
|
69
|
+
npx webspresso db:migrate --force-unlock
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 🛡️ Zero-Downtime Migration Rules for Schema Evolution
|
|
75
|
+
|
|
76
|
+
When modifying production schemas under zero-downtime requirements, follow the **Expand and Contract (Parallel Run)** pattern:
|
|
77
|
+
|
|
78
|
+
| Goal | ❌ Risky (Breaks running old version) | ✅ Zero-Downtime Pattern |
|
|
79
|
+
| :--- | :--- | :--- |
|
|
80
|
+
| **Rename Column** | `table.renameColumn('old', 'new')` | 1. Add `new` column<br>2. Dual-write in application<br>3. Backfill data<br>4. Drop `old` column in subsequent deploy |
|
|
81
|
+
| **Drop Column** | `table.dropColumn('field')` | Remove column references in application code first, deploy code, then drop column in the next deployment. |
|
|
82
|
+
| **Add NOT NULL** | `table.string('col').notNullable()` | 1. Add nullable column or column with default value<br>2. Backfill existing rows<br>3. Alter to `notNullable` |
|
|
83
|
+
| **Add Index** | Raw `CREATE INDEX` (locks table) | In Postgres: use `CREATE INDEX CONCURRENTLY` |
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 🔍 Preflight Verification (`deploy:check`)
|
|
88
|
+
|
|
89
|
+
Webspresso's preflight scanner automatically audits migration strategies:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npx webspresso deploy:check --platform cloudrun --strict
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
If migrations exist and no cloud migration job is detected, it emits an actionable warning to prevent production container race conditions.
|