webspresso 0.0.94 → 0.0.96
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/ADMIN.md +225 -0
- package/.agents/AGENTS.md +72 -0
- package/.agents/ARCHITECTURE.md +119 -0
- package/.agents/AUTH.md +103 -0
- package/.agents/CLI.md +115 -0
- package/.agents/COMPRESSION.md +59 -0
- package/.agents/CONVENTIONS.md +46 -0
- package/.agents/EMAIL.md +141 -0
- package/.agents/ERRORS.md +201 -0
- package/.agents/MCP.md +103 -0
- package/.agents/ORM.md +230 -0
- package/.agents/PLUGINS.md +419 -0
- package/.agents/QUEUE.md +113 -0
- package/.agents/REALTIME.md +213 -0
- package/.agents/ROUTING.md +292 -0
- package/.agents/SERVICES.md +341 -0
- package/.agents/SHUTDOWN.md +67 -0
- package/.agents/STREAMING.md +73 -0
- package/.agents/WORKFLOW.md +27 -0
- package/.agents/internal/DOCUMENTATION_REVIEW.md +485 -0
- package/.agents/internal/TECHNICAL_REVIEW.md +428 -0
- package/.cursorrules +14 -0
- package/AGENTS.md +175 -0
- package/CLAUDE.md +54 -0
- package/README.md +18 -14
- package/bin/commands/agents-init.js +169 -0
- package/bin/commands/api.js +114 -48
- package/bin/commands/doctor.js +35 -0
- package/bin/commands/new.js +13 -2
- package/bin/commands/page.js +75 -89
- package/bin/commands/service.js +77 -0
- package/bin/webspresso.js +2 -2
- package/core/compileSchema.js +1 -1
- package/core/errors/middleware.js +22 -2
- package/core/errors/normalize.js +29 -2
- package/{utils/schemaCache.js → core/schema-cache.js} +2 -3
- package/index.d.ts +306 -0
- package/index.js +35 -1
- package/package.json +19 -6
- package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +22 -1
- package/plugins/csrf/index.js +3 -2
- package/plugins/csv/engine.js +447 -0
- package/plugins/csv/index.js +94 -0
- package/plugins/csv/middleware.js +80 -0
- package/plugins/csv/services.js +138 -0
- package/plugins/file-manager/admin-component.js +613 -0
- package/plugins/file-manager/api-handlers.js +261 -0
- package/plugins/file-manager/core.js +601 -0
- package/plugins/file-manager/index.js +139 -0
- package/plugins/file-manager/security.js +207 -0
- package/plugins/index.js +8 -0
- package/plugins/polar/CHANGELOG.md +18 -0
- package/plugins/polar/README.md +30 -7
- package/plugins/polar/index.js +6 -1
- package/plugins/polar/src/billing-hooks.js +51 -0
- package/plugins/polar/src/config.js +2 -0
- package/plugins/polar/src/rate-limit.js +96 -28
- package/plugins/polar/src/routes.js +3 -2
- package/plugins/polar/src/urls.js +26 -2
- package/plugins/polar/src/user-resolver.js +5 -1
- package/plugins/rate-limit/index.js +5 -1
- package/plugins/xlsx/engine.js +397 -0
- package/plugins/xlsx/index.js +91 -0
- package/plugins/xlsx/middleware.js +87 -0
- package/plugins/xlsx/services.js +118 -0
- package/src/api/api-loader.js +233 -0
- package/src/api/define-api.js +35 -0
- package/src/discovery/file-route-parser.js +201 -0
- package/src/discovery/scan-routes.js +265 -0
- package/src/file-router.js +6 -0
- package/src/modules/define-module.js +99 -0
- package/src/modules/module-discovery.js +117 -0
- package/src/pages/define-page.js +36 -0
- package/src/pages/page-loader.js +214 -0
- package/src/routing/mount-discovered-routes.js +78 -0
- package/src/routing/route-table.js +191 -0
- package/src/server.js +112 -15
- package/src/services/builtins/file-manager.js +178 -0
- package/src/services/index.js +2 -0
- package/bin/commands/skill.js +0 -187
- package/templates/skills/webspresso-usage/REFERENCE-framework.md +0 -289
- package/templates/skills/webspresso-usage/REFERENCE-kernel.md +0 -93
- package/templates/skills/webspresso-usage/SKILL.md +0 -48
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Webspresso HTTP Response Compression Guide
|
|
2
|
+
|
|
3
|
+
[← Back to AGENTS.md](.agents/AGENTS.md)
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Overview
|
|
8
|
+
|
|
9
|
+
Webspresso provides a zero-dependency, streaming-first HTTP response compression layer built entirely with native `node:zlib`. It transparently compresses compressible responses (HTML, JSON, JS, CSS, SVG, XML) using Brotli (`br`), Gzip (`gzip`), or Deflate (`deflate`).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 2. Configuration
|
|
14
|
+
|
|
15
|
+
Compression is disabled by default to avoid unnecessary CPU overhead. Enable via `createApp`:
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
const { app } = createApp({
|
|
19
|
+
pagesDir: './pages',
|
|
20
|
+
server: {
|
|
21
|
+
compression: true, // Enables default 1 KB threshold with Brotli + Gzip
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Advanced Options:
|
|
27
|
+
```js
|
|
28
|
+
const { app } = createApp({
|
|
29
|
+
pagesDir: './pages',
|
|
30
|
+
server: {
|
|
31
|
+
compression: {
|
|
32
|
+
threshold: 2048, // Only compress responses >= 2 KB (default: 1024)
|
|
33
|
+
encodings: ['br', 'gzip'], // Algorithm preference
|
|
34
|
+
level: 6, // Compression level (1-9)
|
|
35
|
+
filter: (req, res) => {
|
|
36
|
+
if (req.path.startsWith('/downloads')) return false;
|
|
37
|
+
return true;
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 3. Route-Level Opt-out
|
|
47
|
+
|
|
48
|
+
Individual routes can opt-out of compression (e.g. to mitigate BREACH attacks or for already-compressed dynamic payloads):
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
// pages/api/sensitive-export.get.js
|
|
52
|
+
module.exports = async function handler(req, res) {
|
|
53
|
+
res.compress(false); // Disables compression for this response
|
|
54
|
+
|
|
55
|
+
res.json({
|
|
56
|
+
secretToken: 'xyz-secret-token',
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
```
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Webspresso Coding Standards & Conventions
|
|
2
|
+
|
|
3
|
+
[← Back to AGENTS.md](.agents/AGENTS.md)
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Core Policies
|
|
8
|
+
|
|
9
|
+
1. **Zero External Dependencies Policy**: Core framework functionality (Auth, JWT, CORS, Routing, Nunjucks helpers) must avoid unnecessary third-party npm dependencies. Rely strictly on native Node.js standard library modules (`crypto`, `path`, `fs`, `events`, `async_hooks`).
|
|
10
|
+
2. **Strict Signature & Schema Verification**: Always verify function signatures and object schemas by inspecting authoritative source files before calling or extending them.
|
|
11
|
+
3. **File & Component Separation**: Keep client-side components in clean standalone JS files (`component.js` / `componentFile`) rather than escaping inline template strings.
|
|
12
|
+
4. **Log & Traceback Inspection**: Base all bug fixes strictly on exact runtime error log tracebacks and empirical test output. Never swallow errors or return dummy fallbacks.
|
|
13
|
+
5. **No Superficial Symptom Patches**: Never resolve errors by masking symptoms or deleting failing unit tests. Always resolve the underlying contract breakage.
|
|
14
|
+
6. **Strict File-Based Routing Policy (NO Express `routes/` or `app.get()`)**:
|
|
15
|
+
- **NEVER** create a `routes/` or `src/routes/` directory.
|
|
16
|
+
- **NEVER** call `express.Router()`, `app.get()`, `app.post()`, or `app.use()` to register application API endpoints.
|
|
17
|
+
- **ALWAYS** define API routes under `pages/api/...` with HTTP method extensions:
|
|
18
|
+
- `GET /api/notes` → `pages/api/notes.get.js`
|
|
19
|
+
- `POST /api/notes` → `pages/api/notes.post.js`
|
|
20
|
+
- `GET /api/notes/:id` → `pages/api/notes/[id].get.js`
|
|
21
|
+
- `DELETE /api/notes/:id` → `pages/api/notes/[id].delete.js`
|
|
22
|
+
- Every file route module must export `{ schema, middleware, handler }` or an `async (req, res)` function. See [.agents/ROUTING.md](ROUTING.md) for full patterns.
|
|
23
|
+
7. **Strict Repository Pattern Policy (NO Raw `db('table')`)**:
|
|
24
|
+
- **NEVER** write raw Knex table queries (`db('table')`, `req.db('table')`) in application code.
|
|
25
|
+
- **ALWAYS** access database models through `db.getRepository('ModelName')` or `req.db.getRepository('ModelName')`.
|
|
26
|
+
- Raw Knex queries bypass schema validation, lifecycle hooks, soft-deletes, multi-tenant scopes, hidden fields, and query caching. See [.agents/ORM.md](ORM.md) for full patterns.
|
|
27
|
+
8. **Services Layer Over Controllers (NO `controllers/`)**:
|
|
28
|
+
- **NEVER** create a `controllers/` directory or write class-based controller singletons.
|
|
29
|
+
- **ALWAYS** encapsulate multi-step business logic, domain mutations, and reusable operations inside `services/domain/action.js`.
|
|
30
|
+
- Invoke services via `ctx.service('domain.action', input)` or `req.service('domain.action', input)`. This ensures declarative validation (`schema`), automatic ACID transaction propagation (`transaction: true`), and role guards (`auth`). See [.agents/SERVICES.md](SERVICES.md).
|
|
31
|
+
9. **Native Dual Auth Over External Packages (NO `jsonwebtoken` or `passport`)**:
|
|
32
|
+
- **NEVER** install third-party auth packages like `jsonwebtoken`, `bcryptjs`, `passport`, or `express-session`.
|
|
33
|
+
- **ALWAYS** use Webspresso's built-in zero-dependency Dual Authentication (`webspresso/core/auth` & `req.auth`). Use `req.auth.attempt()`, `req.auth.generateUserToken()`, and guards `middleware: ['auth']` (web session) or `middleware: ['jwt']` (stateless API Bearer). See [.agents/AUTH.md](AUTH.md).
|
|
34
|
+
10. **Page Data Loaders Over Manual `res.render()`**:
|
|
35
|
+
- **NEVER** manually call `res.render('view.njk', { ... })` inside custom handlers for web pages.
|
|
36
|
+
- **ALWAYS** declare SSR pages inside `pages/` (e.g. `pages/notes/[id].njk`) and provide server-side data via a companion loader in `pages/notes/[id].js` exporting `async function load({ req, res, db, ctx })`.
|
|
37
|
+
11. **Framework Exceptions Over Generic Errors**:
|
|
38
|
+
- **NEVER** return manual error payloads like `return res.status(404).json(...)` inside services or loaders, and never throw generic `throw new Error('Not found')` for client-facing issues (which masks as a 500 error in production).
|
|
39
|
+
- **ALWAYS** throw semantic framework exceptions: `NotFoundError` (404), `ValidationError` (422), `UnauthorizedError` (401), `ForbiddenError` (403), `BadRequestError` (400). Import them from `webspresso` or `webspresso/core/errors`. See [.agents/ERRORS.md](ERRORS.md).
|
|
40
|
+
12. **Background Queue Engine Over External BullMQ or `setTimeout`**:
|
|
41
|
+
- **NEVER** install `bullmq` / `agenda` or use unmanaged `setTimeout()` / `setInterval()` for background or deferred tasks.
|
|
42
|
+
- **ALWAYS** place background job handlers in `jobs/` (e.g. `jobs/email/send.js`) and dispatch them reliably using `req.queue.dispatch('email.send', payload)` or `ctx.queue.dispatch()`. See [.agents/QUEUE.md](QUEUE.md).
|
|
43
|
+
13. **Package Subpath Imports (NO manual `/index.js` suffix)**:
|
|
44
|
+
- **ALWAYS** use published `package.json` export paths: `require('webspresso/core/auth')`, `require('webspresso/core/orm')`, `require('webspresso/plugins/polar/src/webhooks')`.
|
|
45
|
+
- **NEVER** rely on deep relative paths from `node_modules/webspresso/...` or append `/index.js` unless documenting a pre-0.0.95 workaround.
|
|
46
|
+
- Smoke test: `tests/unit/package-exports.test.js`.
|
package/.agents/EMAIL.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Webspresso Email Plugin Guide (`plugins/email`)
|
|
2
|
+
|
|
3
|
+
The `emailPlugin` provides responsive email template compilation (using MJML), Nodemailer transport management, database delivery audit logs, auth email integration (password reset / email verification), and an interactive Admin Panel management UI.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Overview & Setup
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
import { createApp } from 'webspresso';
|
|
11
|
+
import { emailPlugin } from 'webspresso/plugins/email';
|
|
12
|
+
|
|
13
|
+
const email = emailPlugin({
|
|
14
|
+
db,
|
|
15
|
+
smtp: {
|
|
16
|
+
host: 'smtp.mailtrap.io',
|
|
17
|
+
port: 2525,
|
|
18
|
+
auth: { user: 'username', pass: 'password' },
|
|
19
|
+
},
|
|
20
|
+
defaults: {
|
|
21
|
+
from: 'Webspresso App <noreply@example.com>',
|
|
22
|
+
},
|
|
23
|
+
templatesDir: './emails',
|
|
24
|
+
logToDb: true,
|
|
25
|
+
includeAdminPage: true,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const { app } = createApp({
|
|
29
|
+
db,
|
|
30
|
+
plugins: [email],
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 2. Configuration Options
|
|
37
|
+
|
|
38
|
+
| Option | Type | Default | Description |
|
|
39
|
+
|---|---|---|---|
|
|
40
|
+
| `db` | `Object` | `null` | Knex database instance (for logging emails to DB) |
|
|
41
|
+
| `transport` | `Object` | `null` | Pre-configured Nodemailer transport instance |
|
|
42
|
+
| `smtp` | `Object` | `null` | SMTP configuration object `{ host, port, auth }` |
|
|
43
|
+
| `defaults` | `Object` | `{}` | Default email headers (`from`, `replyTo`) |
|
|
44
|
+
| `templatesDir` | `string` | `null` | Path to directory containing `.mjml` templates |
|
|
45
|
+
| `templates` | `Object` | `{}` | In-memory map of MJML template strings |
|
|
46
|
+
| `logToDb` | `boolean` | `true` | Log sent emails to database |
|
|
47
|
+
| `tableName` | `string` | `'email_logs'` | Table name used for email logs |
|
|
48
|
+
| `includeAdminPage` | `boolean` | `true` | Mount Email management UI inside Admin Panel (`/_admin/email`) |
|
|
49
|
+
| `authEmails` | `Object` | `null` | Auth email bridge configuration |
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 3. Template Management & MJML Compilation
|
|
54
|
+
|
|
55
|
+
Place `.mjml` templates inside `templatesDir` (e.g. `emails/welcome.mjml`):
|
|
56
|
+
|
|
57
|
+
```xml
|
|
58
|
+
<mjml>
|
|
59
|
+
<mj-body>
|
|
60
|
+
<mj-section>
|
|
61
|
+
<mj-column>
|
|
62
|
+
<mj-text font-size="20px">Welcome, {{ name }}!</mj-text>
|
|
63
|
+
<mj-button href="{{ activationUrl }}">Activate Account</mj-button>
|
|
64
|
+
</mj-column>
|
|
65
|
+
</mj-section>
|
|
66
|
+
</mj-body>
|
|
67
|
+
</mjml>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Templates are dynamically compiled to responsive HTML with Nunjucks variable interpolation.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 4. Plugin API (`emailPlugin().api`)
|
|
75
|
+
|
|
76
|
+
The plugin exports helper methods under `.api`:
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
const { api: emailApi } = email;
|
|
80
|
+
|
|
81
|
+
// 1. Send compiled MJML template
|
|
82
|
+
await emailApi.sendTemplate('welcome', {
|
|
83
|
+
to: 'user@example.com',
|
|
84
|
+
subject: 'Welcome to Webspresso!',
|
|
85
|
+
data: { name: 'Ahmet', activationUrl: 'https://example.com/activate' },
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// 2. Direct raw email sending
|
|
89
|
+
await emailApi.send({
|
|
90
|
+
to: 'user@example.com',
|
|
91
|
+
subject: 'Hello',
|
|
92
|
+
html: '<p>Direct HTML</p>',
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// 3. Render HTML preview without sending
|
|
96
|
+
const { html, subject } = await emailApi.preview('welcome', { name: 'Ahmet' });
|
|
97
|
+
|
|
98
|
+
// 4. Register in-memory MJML template string dynamically
|
|
99
|
+
emailApi.registerTemplate('notification', '<mjml><mj-body>...</mj-body></mjml>');
|
|
100
|
+
|
|
101
|
+
// 5. Connection test & log management
|
|
102
|
+
const isReady = await emailApi.verifyConnection();
|
|
103
|
+
const logs = await emailApi.queryLogs({ status: 'sent', limit: 20 });
|
|
104
|
+
await emailApi.purgeLogs({ olderThan: '30d' });
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 5. Auth Email Bridge (`authEmails`)
|
|
110
|
+
|
|
111
|
+
Automatically wires password reset and email verification flows with `AuthManager`:
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
const email = emailPlugin({
|
|
115
|
+
db,
|
|
116
|
+
auth,
|
|
117
|
+
authEmails: {
|
|
118
|
+
enabled: true,
|
|
119
|
+
resetPassword: {
|
|
120
|
+
template: 'reset-password',
|
|
121
|
+
subject: 'Reset your password',
|
|
122
|
+
tokenExpiresIn: '1h',
|
|
123
|
+
},
|
|
124
|
+
emailVerification: {
|
|
125
|
+
template: 'verify-email',
|
|
126
|
+
subject: 'Verify your email address',
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## 6. Admin Panel UI & Database Logs
|
|
135
|
+
|
|
136
|
+
When `includeAdminPage: true` and `adminPanelPlugin` are present, an interactive Email Management module is mounted at `/_admin/email`:
|
|
137
|
+
|
|
138
|
+
- **Live Template Preview**: Select any MJML template, edit mock JSON data, and preview rendered responsive HTML.
|
|
139
|
+
- **Test Sender**: Send test emails directly from the Admin Panel to verify SMTP configuration.
|
|
140
|
+
- **Audit Logs Inspector**: Filter sent email logs by status (`sent`, `failed`), view error tracebacks, and purge historic logs.
|
|
141
|
+
- **Database Schema**: Log entries are stored in `email_logs` table (`id`, `to_email`, `subject`, `template_name`, `status`, `error`, `created_at`).
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Webspresso Framework Exceptions & Error Handling Guide
|
|
2
|
+
|
|
3
|
+
[← Back to AGENTS.md](.agents/AGENTS.md)
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## ⚠️ STRICT RULE: Throw Semantic Errors (NO Generic `new Error()` or Manual `res.status`)
|
|
8
|
+
|
|
9
|
+
- **NEVER** return manual error payloads like `return res.status(404).json(...)` inside services or loaders.
|
|
10
|
+
- **NEVER** throw a generic `throw new Error('Not found')` for business or client-facing issues. In production, unmapped `Error` instances are automatically masked as `500 Internal Server Error` with stack traces hidden!
|
|
11
|
+
- **ALWAYS** throw semantic framework exceptions:
|
|
12
|
+
- `throw new NotFoundError('Item not found')` → returns HTTP 404
|
|
13
|
+
- `throw new ValidationError('Invalid data', { fields: { ... } })` → returns HTTP 422
|
|
14
|
+
- `throw new UnauthorizedError('Please log in')` → returns HTTP 401
|
|
15
|
+
- `throw new ForbiddenError('Access denied')` → returns HTTP 403
|
|
16
|
+
- `throw new BadRequestError('Invalid query')` → returns HTTP 400
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 1. Overview & Exception Hierarchy
|
|
21
|
+
|
|
22
|
+
Webspresso features a centralized, lightweight, zero-dependency exception hierarchy inspired by Django's failure domain separation. Thrown synchronous and asynchronous exceptions are automatically caught by the global error boundary, normalized, and formatted as standardized JSON responses for API routes or styled HTML pages for web clients.
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
WebspressoError (base class with cause, code, details)
|
|
26
|
+
├── ConfigurationError (bootstrap & config errors)
|
|
27
|
+
├── PluginError (plugin lifecycle & dependency errors)
|
|
28
|
+
├── ValidationError (validation failures with fields, 422 default)
|
|
29
|
+
├── SecurityError (suspicious requests, CSRF/cookie tampering, 400 default)
|
|
30
|
+
├── RequestError
|
|
31
|
+
│ └── RequestAbortedError (client socket disconnection, 499 default)
|
|
32
|
+
├── RouterError
|
|
33
|
+
│ ├── RouteNotFoundError (404 URL resolution error)
|
|
34
|
+
│ └── RouteGenerationError (named route generation error)
|
|
35
|
+
└── HttpError (base for HTTP-mapped exceptions)
|
|
36
|
+
├── BadRequestError (400)
|
|
37
|
+
├── UnauthorizedError (401)
|
|
38
|
+
├── ForbiddenError (403)
|
|
39
|
+
├── NotFoundError (404)
|
|
40
|
+
├── MethodNotAllowedError (405)
|
|
41
|
+
├── ConflictError (409)
|
|
42
|
+
├── PayloadTooLargeError (413)
|
|
43
|
+
├── UnsupportedMediaTypeError (415)
|
|
44
|
+
├── UnprocessableEntityError (422)
|
|
45
|
+
└── TooManyRequestsError (429)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 2. Public Imports & Subpath
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
import {
|
|
54
|
+
WebspressoError,
|
|
55
|
+
HttpError,
|
|
56
|
+
BadRequestError,
|
|
57
|
+
UnauthorizedError,
|
|
58
|
+
ForbiddenError,
|
|
59
|
+
NotFoundError,
|
|
60
|
+
MethodNotAllowedError,
|
|
61
|
+
ConflictError,
|
|
62
|
+
PayloadTooLargeError,
|
|
63
|
+
UnsupportedMediaTypeError,
|
|
64
|
+
UnprocessableEntityError,
|
|
65
|
+
TooManyRequestsError,
|
|
66
|
+
ValidationError,
|
|
67
|
+
ConfigurationError,
|
|
68
|
+
PluginError,
|
|
69
|
+
SecurityError,
|
|
70
|
+
RequestAbortedError,
|
|
71
|
+
RouteNotFoundError,
|
|
72
|
+
RouteGenerationError,
|
|
73
|
+
} from 'webspresso';
|
|
74
|
+
|
|
75
|
+
// Or via subpath:
|
|
76
|
+
import { NotFoundError, ValidationError } from 'webspresso/errors';
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 3. Usage Examples
|
|
82
|
+
|
|
83
|
+
### 3.1 HTTP Exceptions in API Handlers
|
|
84
|
+
```js
|
|
85
|
+
// pages/api/users/[id].get.js
|
|
86
|
+
import { NotFoundError } from 'webspresso';
|
|
87
|
+
|
|
88
|
+
module.exports = async function handler(req, res) {
|
|
89
|
+
const user = await req.db.getRepository('User').findById(req.params.id);
|
|
90
|
+
if (!user) {
|
|
91
|
+
throw new NotFoundError('User not found', {
|
|
92
|
+
code: 'USER_NOT_FOUND',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
res.json({ user });
|
|
96
|
+
};
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 3.2 Structured Validation Errors (422)
|
|
100
|
+
```js
|
|
101
|
+
import { ValidationError } from 'webspresso';
|
|
102
|
+
|
|
103
|
+
throw new ValidationError('Validation failed', {
|
|
104
|
+
code: 'INVALID_INPUT',
|
|
105
|
+
fields: {
|
|
106
|
+
email: ['Invalid email address'],
|
|
107
|
+
age: ['Must be at least 18'],
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 3.3 Custom Error Classes
|
|
113
|
+
```js
|
|
114
|
+
import { HttpError } from 'webspresso';
|
|
115
|
+
|
|
116
|
+
export class PaymentRequiredError extends HttpError {
|
|
117
|
+
constructor(message = 'Payment Required', options = {}) {
|
|
118
|
+
super(402, message, {
|
|
119
|
+
code: options.code || 'PAYMENT_REQUIRED',
|
|
120
|
+
expose: true,
|
|
121
|
+
...options,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### 3.4 Custom Error Handler (`app.setErrorHandler`)
|
|
128
|
+
```js
|
|
129
|
+
const { app } = createApp({ pagesDir: './pages' });
|
|
130
|
+
|
|
131
|
+
app.setErrorHandler(async (err, req, res, next) => {
|
|
132
|
+
if (err instanceof ValidationError) {
|
|
133
|
+
return res.status(422).json({
|
|
134
|
+
success: false,
|
|
135
|
+
errors: err.fields,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
// Re-throwing or returning unhandled delegates to Webspresso default handler
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## 4. Response Contract & Production Masking
|
|
145
|
+
|
|
146
|
+
### Default JSON Contract:
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"status": 404,
|
|
150
|
+
"error": "Not Found",
|
|
151
|
+
"message": "User not found",
|
|
152
|
+
"code": "USER_NOT_FOUND"
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Development vs Production:
|
|
157
|
+
- **Production (`NODE_ENV === 'production'`)**: 500 errors are masked to `"Internal Server Error"`. `stack`, `cause`, and internal file paths are never exposed to clients.
|
|
158
|
+
- **Development**: Returns `stack`, `cause`, and a structured `trace` object identifying the exact route, module, source file, and execution phase.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 5. Route Tracing & Request ID Observability
|
|
163
|
+
|
|
164
|
+
Webspresso automatically correlates every request and enriches thrown errors with route-level execution metadata:
|
|
165
|
+
|
|
166
|
+
### 5.1 Request Correlation ID (`req.id` / `X-Request-Id`)
|
|
167
|
+
Every incoming request is assigned a unique UUID (`req.id`) available on `req.id` and returned in the `X-Request-Id` response header:
|
|
168
|
+
```javascript
|
|
169
|
+
// Access in services, loaders, or logs:
|
|
170
|
+
const reqId = req.id; // e.g. "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### 5.2 Context-Enriched Trace Object (Development Mode)
|
|
174
|
+
When an exception occurs during request execution in `definePage`, `defineApi`, or a feature module, the development JSON payload contains an explicit `trace` object:
|
|
175
|
+
|
|
176
|
+
```json
|
|
177
|
+
{
|
|
178
|
+
"status": 500,
|
|
179
|
+
"error": "Internal Server Error",
|
|
180
|
+
"message": "Database query failed",
|
|
181
|
+
"trace": {
|
|
182
|
+
"route": "/api/auth/me",
|
|
183
|
+
"method": "GET",
|
|
184
|
+
"source": "src/modules/auth/api/me.get.js",
|
|
185
|
+
"module": "auth",
|
|
186
|
+
"phase": "handler",
|
|
187
|
+
"requestId": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
|
|
188
|
+
},
|
|
189
|
+
"stack": "Error: Database query failed\n at ...",
|
|
190
|
+
"cause": null
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### 5.3 Visual Console Error Banners
|
|
195
|
+
In server logs, 500 errors are automatically highlighted with visual banners pinpointing the root cause:
|
|
196
|
+
```txt
|
|
197
|
+
[webspresso] 🚨 Error at GET /api/auth/me [module: auth] (reqId: 9b1deb4d-3b7d-...)
|
|
198
|
+
Source: src/modules/auth/api/me.get.js
|
|
199
|
+
Phase: handler
|
|
200
|
+
```
|
|
201
|
+
|
package/.agents/MCP.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Webspresso Model Context Protocol (MCP) Guide
|
|
2
|
+
|
|
3
|
+
The **Model Context Protocol (MCP)** plugin (`plugins/mcp`) connects Webspresso applications directly to AI agents, IDE assistants (Antigravity, Claude Desktop, Cursor, Zed, Windsurf), and autonomous coding tools.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Overview & Architecture
|
|
8
|
+
|
|
9
|
+
Webspresso's architecture seamlessly bridges to MCP:
|
|
10
|
+
- **Services (`services/`) → MCP Tools**: Every service defined in `services/` is automatically converted into an MCP tool with full JSON Schema input validation derived from its Zod `schema`.
|
|
11
|
+
- **ORM Models (`models/`) → MCP Tools & Resources**: Models defined with `defineModel` provide query tools (`orm_find`, `orm_findById`), mutation tools (`orm_create`, `orm_update`, `orm_delete`), metadata tools (`orm_describe_model`), and resources (`webspresso://models`, `webspresso://models/{model}`, `webspresso://schema`).
|
|
12
|
+
- **System Context → MCP Resources**: `webspresso://routes` (file routes, API routes, HTTP methods), `webspresso://services` (catalog of services), and `webspresso://health` (system metrics).
|
|
13
|
+
- **Dual Transport**:
|
|
14
|
+
- **Stdio (`webspresso mcp`)**: Standard input/output JSON-RPC 2.0 lines for local IDEs and CLI.
|
|
15
|
+
- **HTTP & SSE (`/_mcp/sse`, `/_mcp/messages`, `/_mcp`)**: Server-Sent Events stream and Streamable HTTP for remote or web-accessible agents.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 2. Using the Plugin in Webspresso App
|
|
20
|
+
|
|
21
|
+
```javascript
|
|
22
|
+
const { createApp, mcpPlugin } = require('webspresso');
|
|
23
|
+
const db = require('./models');
|
|
24
|
+
|
|
25
|
+
const { app } = createApp({
|
|
26
|
+
db,
|
|
27
|
+
plugins: [
|
|
28
|
+
mcpPlugin({
|
|
29
|
+
path: '/_mcp', // default: '/_mcp'
|
|
30
|
+
auth: {
|
|
31
|
+
token: process.env.MCP_SECRET_TOKEN, // Bearer token validation
|
|
32
|
+
localhostOnly: true, // Only allow connections from localhost
|
|
33
|
+
},
|
|
34
|
+
services: {
|
|
35
|
+
include: ['user.*', 'catalog.*'], // optional filter
|
|
36
|
+
readOnly: false, // set true to disable mutation services
|
|
37
|
+
},
|
|
38
|
+
orm: {
|
|
39
|
+
readOnly: false, // set true to allow only find/describe queries
|
|
40
|
+
},
|
|
41
|
+
discovery: true, // auto-discover custom mcp/ folder
|
|
42
|
+
}),
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 3. CLI Command: `webspresso mcp`
|
|
50
|
+
|
|
51
|
+
Start the MCP server over Stdio without starting the web server:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Start MCP server over stdio
|
|
55
|
+
npx webspresso mcp
|
|
56
|
+
|
|
57
|
+
# Restrict to read-only mode (prevent mutations)
|
|
58
|
+
npx webspresso mcp --read-only
|
|
59
|
+
|
|
60
|
+
# Print configuration snippet for Claude Desktop / Cursor
|
|
61
|
+
npx webspresso mcp --print-config
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### IDE Configuration
|
|
65
|
+
|
|
66
|
+
Add to `claude_desktop_config.json` or `.cursor/mcp.json` or Antigravity IDE `mcp_config.json`:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"mcpServers": {
|
|
71
|
+
"my-webspresso-app": {
|
|
72
|
+
"command": "npx",
|
|
73
|
+
"args": ["webspresso", "mcp"]
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 4. Custom Tools, Resources, and Prompts (`mcp/` folder)
|
|
82
|
+
|
|
83
|
+
When `discovery: true` (default), Webspresso scans the project's `mcp/` directory:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
mcp/
|
|
87
|
+
├── tools/
|
|
88
|
+
│ └── deploy_preview.js # exports { name, description, inputSchema, handler }
|
|
89
|
+
├── resources/
|
|
90
|
+
│ └── docs.js # exports { uri, name, description, handler }
|
|
91
|
+
└── prompts/
|
|
92
|
+
└── review_code.js # exports { name, description, arguments, handler }
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Programmatic Registration via Runtime API
|
|
96
|
+
```javascript
|
|
97
|
+
const mcp = app.usePlugin('mcp'); // or ctx.mcp inside plugins
|
|
98
|
+
mcp.registerTool({
|
|
99
|
+
name: 'custom_action',
|
|
100
|
+
description: 'Does something custom',
|
|
101
|
+
handler: async (args, ctx) => { ... },
|
|
102
|
+
});
|
|
103
|
+
```
|