webspresso 0.0.95 → 0.0.97
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/.agents/skills/webspresso-admin-and-plugins/SKILL.md +122 -0
- package/.agents/skills/webspresso-framework/SKILL.md +56 -0
- package/.agents/skills/webspresso-orm-and-database/SKILL.md +119 -0
- package/.agents/skills/webspresso-routing-and-api/SKILL.md +114 -0
- package/.agents/skills/webspresso-services/SKILL.md +101 -0
- package/.cursorrules +14 -0
- package/AGENTS.md +175 -0
- package/CLAUDE.md +54 -0
- package/README.md +18 -14
- package/bin/commands/admin-password.js +2 -1
- package/bin/commands/admin-setup.js +1 -1
- package/bin/commands/agents-init.js +209 -0
- package/bin/commands/api.js +114 -48
- package/bin/commands/audit-prune.js +1 -1
- package/bin/commands/doctor.js +35 -0
- package/bin/commands/favicon-generate.js +1 -1
- package/bin/commands/new.js +13 -2
- package/bin/commands/orm-map.js +5 -4
- package/bin/commands/page.js +75 -89
- package/bin/commands/polar-migrate.js +1 -1
- package/bin/commands/seed.js +1 -1
- package/bin/commands/service.js +77 -0
- package/bin/webspresso.js +2 -2
- package/core/auth/hash.js +10 -5
- 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 +36 -14
- package/plugins/admin-panel/app.js +10 -9
- package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +22 -1
- package/plugins/admin-panel/core/api-extensions.js +4 -0
- package/plugins/basic-auth/index.js +3 -1
- package/plugins/content/client/inline-edit.js +12 -12
- package/plugins/cors.js +6 -7
- 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 +217 -0
- package/plugins/index.js +45 -79
- package/plugins/mcp/transports/sse.js +4 -2
- package/plugins/site-analytics/admin-component.js +7 -8
- 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 +9 -1
- 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/discovery.js +6 -1
- package/src/services/index.js +6 -11
- 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,119 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: webspresso-orm-and-database
|
|
3
|
+
description: >-
|
|
4
|
+
Define models, run queries, handle transactions, manage relations, and scaffold migrations in
|
|
5
|
+
Webspresso ORM. Use when writing database models (defineModel), using zdb schema builders,
|
|
6
|
+
querying repositories (db.getRepository), configuring query cache, soft deletes, and ambient transactions.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Webspresso ORM & Database Skill
|
|
10
|
+
|
|
11
|
+
⚠️ **CRITICAL ANTI-PATTERN:** Never write raw Knex table queries (e.g. `db('users')` or `knex('users')`) in application code. Always use Repositories (`db.getRepository('ModelName')`).
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. Defining Models (`models/*.js`)
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
const { defineModel, zdb } = require('webspresso');
|
|
19
|
+
|
|
20
|
+
module.exports = defineModel({
|
|
21
|
+
name: 'Product',
|
|
22
|
+
table: 'products',
|
|
23
|
+
primaryKey: 'id',
|
|
24
|
+
|
|
25
|
+
schema: zdb.object({
|
|
26
|
+
id: zdb.id(),
|
|
27
|
+
name: zdb.string().min(1).max(255),
|
|
28
|
+
slug: zdb.string().unique(),
|
|
29
|
+
price: zdb.decimal({ precision: 10, scale: 2 }),
|
|
30
|
+
in_stock: zdb.boolean().default(true),
|
|
31
|
+
metadata: zdb.json().optional(),
|
|
32
|
+
user_id: zdb.foreignKey('users.id'),
|
|
33
|
+
created_at: zdb.timestamp().autoCreate(),
|
|
34
|
+
updated_at: zdb.timestamp().autoUpdate(),
|
|
35
|
+
deleted_at: zdb.timestamp().nullable(),
|
|
36
|
+
}),
|
|
37
|
+
|
|
38
|
+
relations: {
|
|
39
|
+
user: {
|
|
40
|
+
type: 'belongsTo',
|
|
41
|
+
model: 'User',
|
|
42
|
+
foreignKey: 'user_id',
|
|
43
|
+
},
|
|
44
|
+
reviews: {
|
|
45
|
+
type: 'hasMany',
|
|
46
|
+
model: 'Review',
|
|
47
|
+
foreignKey: 'product_id',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
scopes: {
|
|
52
|
+
softDelete: true, // enables deleted_at filtering automatically
|
|
53
|
+
active: (query) => query.where('in_stock', true),
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
hidden: ['metadata.internal_notes'], // stripped from output
|
|
57
|
+
|
|
58
|
+
cache: 'auto', // query caching ('auto' | 'smart' | true)
|
|
59
|
+
|
|
60
|
+
admin: {
|
|
61
|
+
enabled: true,
|
|
62
|
+
label: 'Products',
|
|
63
|
+
icon: 'package',
|
|
64
|
+
columns: ['name', 'price', 'in_stock', 'created_at'],
|
|
65
|
+
search: ['name', 'slug'],
|
|
66
|
+
filters: ['in_stock', 'user_id'],
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 2. Repositories API
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
const repo = db.getRepository('Product');
|
|
77
|
+
|
|
78
|
+
// Single Record Queries
|
|
79
|
+
const item = await repo.findById(id);
|
|
80
|
+
const item = await repo.findOne({ slug: 'espresso-blend' });
|
|
81
|
+
|
|
82
|
+
// Multiple Records & Filtering
|
|
83
|
+
const activeItems = await repo.find({ in_stock: true });
|
|
84
|
+
|
|
85
|
+
// Insert & Update (auto-validates with schema & triggers lifecycle hooks)
|
|
86
|
+
const created = await repo.create({
|
|
87
|
+
name: 'Espresso Blend',
|
|
88
|
+
slug: 'espresso-blend',
|
|
89
|
+
price: 14.99,
|
|
90
|
+
user_id: user.id,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const updated = await repo.update(created.id, { price: 16.99 });
|
|
94
|
+
|
|
95
|
+
// Deletion (respects softDelete scope)
|
|
96
|
+
await repo.delete(id);
|
|
97
|
+
|
|
98
|
+
// Fluent Query Builder with Eager Loading & Pagination
|
|
99
|
+
const results = await repo.query()
|
|
100
|
+
.where('price', '>=', 10)
|
|
101
|
+
.include('user', 'reviews')
|
|
102
|
+
.orderBy('created_at', 'desc')
|
|
103
|
+
.paginate({ page: 1, limit: 20 });
|
|
104
|
+
// Returns: { data: [...], pagination: { total, page, limit, totalPages } }
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 3. Ambient Transactions (`AsyncLocalStorage`)
|
|
110
|
+
|
|
111
|
+
Inside `db.transaction()` or services declared with `transaction: true`, all repository operations automatically bind to the ambient transaction without manual `trx` passing:
|
|
112
|
+
|
|
113
|
+
```javascript
|
|
114
|
+
await db.transaction(async () => {
|
|
115
|
+
const user = await db.getRepository('User').create(userData);
|
|
116
|
+
const account = await db.getRepository('Account').create({ userId: user.id, balance: 100 });
|
|
117
|
+
// Automatically commits if promise resolves, or rolls back if an exception is thrown
|
|
118
|
+
});
|
|
119
|
+
```
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: webspresso-routing-and-api
|
|
3
|
+
description: >-
|
|
4
|
+
Build and declare file-based API endpoints and SSR pages in Webspresso using defineApi, definePage,
|
|
5
|
+
and defineModule. Use when writing route handlers, data loaders (load()), parameter brackets ([id]),
|
|
6
|
+
Zod input validation, response streaming, and route middleware.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Webspresso Routing & API Skill
|
|
10
|
+
|
|
11
|
+
Webspresso uses strictly file-based routing. Never use `express.Router()`, `app.get()`, or a `routes/` directory.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. File-Based API Endpoints (`pages/api/...`)
|
|
16
|
+
|
|
17
|
+
All API routes live in `pages/api/` and use HTTP method extensions:
|
|
18
|
+
- `pages/api/notes.get.js` → `GET /api/notes`
|
|
19
|
+
- `pages/api/notes.post.js` → `POST /api/notes`
|
|
20
|
+
- `pages/api/notes/[id].get.js` → `GET /api/notes/:id`
|
|
21
|
+
- `pages/api/notes/[id].delete.js` → `DELETE /api/notes/:id`
|
|
22
|
+
|
|
23
|
+
### Standard `defineApi` Contract
|
|
24
|
+
|
|
25
|
+
```javascript
|
|
26
|
+
const { defineApi } = require('webspresso');
|
|
27
|
+
|
|
28
|
+
module.exports = defineApi({
|
|
29
|
+
schema: ({ z }) => ({
|
|
30
|
+
body: z.object({
|
|
31
|
+
title: z.string().min(1, 'Title is required'),
|
|
32
|
+
content: z.string().optional(),
|
|
33
|
+
}),
|
|
34
|
+
params: z.object({
|
|
35
|
+
id: z.string().optional(),
|
|
36
|
+
}),
|
|
37
|
+
query: z.object({
|
|
38
|
+
filter: z.string().optional(),
|
|
39
|
+
}),
|
|
40
|
+
}),
|
|
41
|
+
middleware: ['auth'], // optional named middleware string or array of middlewares
|
|
42
|
+
handler: async (req, res, ctx) => {
|
|
43
|
+
// 1. Validated input is automatically available in req.input
|
|
44
|
+
const { title, content } = req.input.body;
|
|
45
|
+
|
|
46
|
+
// 2. Repositories and Services are injected
|
|
47
|
+
const note = await req.db.getRepository('Note').create({ title, content });
|
|
48
|
+
|
|
49
|
+
// 3. Return response
|
|
50
|
+
return res.status(201).json(note);
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 2. SSR HTML Pages & Data Loaders (`pages/...`)
|
|
58
|
+
|
|
59
|
+
- **Template**: `pages/products/[id].njk`
|
|
60
|
+
- **Companion Loader**: `pages/products/[id].js`
|
|
61
|
+
|
|
62
|
+
### Companion Loader (`definePage`)
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
const { definePage } = require('webspresso');
|
|
66
|
+
|
|
67
|
+
module.exports = definePage({
|
|
68
|
+
middleware: ['auth'], // optional
|
|
69
|
+
head: {
|
|
70
|
+
title: 'Product Details',
|
|
71
|
+
},
|
|
72
|
+
load: async ({ req, res, db, ctx }) => {
|
|
73
|
+
const product = await db.getRepository('Product').findById(req.params.id);
|
|
74
|
+
if (!product) {
|
|
75
|
+
throw new ctx.errors.NotFoundError('Product not found');
|
|
76
|
+
}
|
|
77
|
+
return { product, title: product.name };
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Template (`.njk`)
|
|
83
|
+
|
|
84
|
+
```nunjucks
|
|
85
|
+
{% extends "layout.njk" %}
|
|
86
|
+
|
|
87
|
+
{% block content %}
|
|
88
|
+
<div class="product-page">
|
|
89
|
+
<h1>{{ product.name }}</h1>
|
|
90
|
+
<p class="price">{{ fsy.formatCurrency(product.price) }}</p>
|
|
91
|
+
<p>{{ product.description }}</p>
|
|
92
|
+
</div>
|
|
93
|
+
{% endblock %}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## 3. Modular Domain Architecture (`defineModule`)
|
|
99
|
+
|
|
100
|
+
Encapsulate feature domains inside `src/modules/` or `modules/`:
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
const { defineModule } = require('webspresso');
|
|
104
|
+
|
|
105
|
+
module.exports = defineModule({
|
|
106
|
+
name: 'billing',
|
|
107
|
+
pages: './pages', // auto-discovers pages under /billing/*
|
|
108
|
+
api: './api', // auto-discovers API under /api/billing/*
|
|
109
|
+
services: './services', // auto-discovers services under 'billing.*'
|
|
110
|
+
middlewares: {
|
|
111
|
+
paidOnly: (req, res, next) => { ... },
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
```
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: webspresso-services
|
|
3
|
+
description: >-
|
|
4
|
+
Encapsulate business logic, validations, transactions, and authorizations in Webspresso services.
|
|
5
|
+
Use when writing service files (services/domain/action.js), defining schemas (defineService),
|
|
6
|
+
configuring transaction: true, calling ctx.service() or req.service(), and adding RBAC guards.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Webspresso Services Layer Skill
|
|
10
|
+
|
|
11
|
+
⚠️ **CRITICAL ANTI-PATTERN:** Never create a `controllers/` folder or controller classes. Encapsulate multi-step business logic in `services/` and invoke via `ctx.service()` or `req.service()`.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. File-Based Auto-Discovery (`services/`)
|
|
16
|
+
|
|
17
|
+
Files in `services/` map directly to dot-separated service names:
|
|
18
|
+
- `services/user/create.js` → `'user.create'`
|
|
19
|
+
- `services/order/checkout.js` → `'order.checkout'`
|
|
20
|
+
- `services/billing/process-refund.js` → `'billing.process-refund'` (and `'billing.processRefund'`)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 2. Defining Services (`defineService`)
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
const { defineService } = require('webspresso');
|
|
28
|
+
|
|
29
|
+
module.exports = defineService({
|
|
30
|
+
schema: ({ z }) => z.object({
|
|
31
|
+
userId: z.number().int().positive(),
|
|
32
|
+
amount: z.number().positive(),
|
|
33
|
+
reason: z.string().min(3),
|
|
34
|
+
}),
|
|
35
|
+
|
|
36
|
+
// Optional Declarative Auth / Role Guard
|
|
37
|
+
auth: ['admin', 'manager'], // or boolean true, or (ctx, input) => boolean
|
|
38
|
+
|
|
39
|
+
// Automatic ACID Transaction Propagation
|
|
40
|
+
transaction: true,
|
|
41
|
+
|
|
42
|
+
// Optional Service Result Cache
|
|
43
|
+
cache: {
|
|
44
|
+
ttl: 300, // seconds
|
|
45
|
+
key: (input) => `refund:${input.userId}:${input.amount}`,
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
// Execution Handler
|
|
49
|
+
handler: async (input, ctx) => {
|
|
50
|
+
// ctx.db, ctx.service, ctx.user, ctx.logger are automatically injected
|
|
51
|
+
const userRepo = ctx.db.getRepository('User');
|
|
52
|
+
const accountRepo = ctx.db.getRepository('Account');
|
|
53
|
+
|
|
54
|
+
const user = await userRepo.findById(input.userId);
|
|
55
|
+
if (!user) {
|
|
56
|
+
throw new ctx.errors.NotFoundError('User not found');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const account = await accountRepo.findOne({ userId: user.id });
|
|
60
|
+
const updatedAccount = await accountRepo.update(account.id, {
|
|
61
|
+
balance: account.balance + input.amount,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Nested service call shares the same ambient transaction automatically
|
|
65
|
+
await ctx.service('audit.log', {
|
|
66
|
+
action: 'refund.processed',
|
|
67
|
+
userId: user.id,
|
|
68
|
+
amount: input.amount,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
success: true,
|
|
73
|
+
balance: updatedAccount.balance,
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 3. Invoking Services
|
|
82
|
+
|
|
83
|
+
From SSR Companion Loaders:
|
|
84
|
+
```javascript
|
|
85
|
+
module.exports = definePage({
|
|
86
|
+
load: async ({ ctx }) => {
|
|
87
|
+
const data = await ctx.service('order.summary', { userId: ctx.user.id });
|
|
88
|
+
return { data };
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
From API Route Handlers:
|
|
94
|
+
```javascript
|
|
95
|
+
module.exports = defineApi({
|
|
96
|
+
handler: async (req, res) => {
|
|
97
|
+
const result = await req.service('order.checkout', req.input.body);
|
|
98
|
+
return res.status(200).json(result);
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
```
|
package/.cursorrules
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Cursor Rules for Webspresso
|
|
2
|
+
|
|
3
|
+
Webspresso is a full-stack Node.js framework with 100% file-based routing, Knex ORM repositories, and zero external dependencies for core utilities.
|
|
4
|
+
|
|
5
|
+
## Critical Guidelines
|
|
6
|
+
- Complete reference & guidebook: Read `AGENTS.md` and `.agents/AGENTS.md`.
|
|
7
|
+
- Canonical API definitions: Check `index.d.ts`.
|
|
8
|
+
|
|
9
|
+
## Strict Anti-Patterns & Requirements
|
|
10
|
+
1. **NO `routes/` or `app.get()`**: All API routes belong in `pages/api/...` with method extensions (`.get.js`, `.post.js`, `.delete.js`, `.patch.js`, `.put.js`).
|
|
11
|
+
2. **NO Raw `db('table')`**: Always use `req.db.getRepository('ModelName')` or `db.getRepository('ModelName')`.
|
|
12
|
+
3. **NO `controllers/`**: Encapsulate multi-step business logic in `services/domain/action.js` and call via `ctx.service('domain.action', input)`.
|
|
13
|
+
4. **NO external auth libraries**: Use built-in `req.auth` and `webspresso/core/auth`. Do not install `jsonwebtoken` or `passport`.
|
|
14
|
+
5. **Zero regressions**: Run unit/integration tests with `npx vitest run tests/...` before completing tasks.
|
package/AGENTS.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Webspresso Project Guidelines & Rules
|
|
2
|
+
|
|
3
|
+
Webspresso is a lightweight, zero-dependency-sprawl full-stack Node.js SSR & API framework featuring Next.js/Nuxt-style file-based routing, an intuitive Knex-based ORM, a modular plugin ecosystem, and a customizable Mithril.js SPA Admin Panel.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🎯 Agent Intent Triage Matrix (Quick Guide Selection)
|
|
8
|
+
|
|
9
|
+
When tasked with a feature or bugfix, **immediately consult the corresponding topic guide** before writing code:
|
|
10
|
+
|
|
11
|
+
| Intent / Task | Canonical Topic Guide | Primary Directory / Ground Truth |
|
|
12
|
+
| :--- | :--- | :--- |
|
|
13
|
+
| **New API Route / Endpoint** | [`.agents/ROUTING.md`](.agents/ROUTING.md) | `pages/api/...` |
|
|
14
|
+
| **SSR HTML Page / Layout / Loader** | [`.agents/ROUTING.md`](.agents/ROUTING.md) & [`.agents/STREAMING.md`](.agents/STREAMING.md) | `pages/*.njk`, `pages/*.js`, `views/` |
|
|
15
|
+
| **Database Model / Migration / Query** | [`.agents/ORM.md`](.agents/ORM.md) | `models/*.js`, `core/orm`, `migrations/` |
|
|
16
|
+
| **Business Logic / Transaction / Service** | [`.agents/SERVICES.md`](.agents/SERVICES.md) | `services/` |
|
|
17
|
+
| **Admin Panel (Page, Widget, Action, UI)** | [`.agents/ADMIN.md`](.agents/ADMIN.md) | `plugins/admin-panel/` |
|
|
18
|
+
| **Authentication (Session / JWT / Guard)** | [`.agents/AUTH.md`](.agents/AUTH.md) | `core/auth/` |
|
|
19
|
+
| **Background Jobs & Worker Queue** | [`.agents/QUEUE.md`](.agents/QUEUE.md) | `jobs/` |
|
|
20
|
+
| **API Signatures & Types Verification** | [`index.d.ts`](index.d.ts) | **Canonical Ground Truth** |
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 🛡️ 4-Step Mandatory Agent Workflow Protocol
|
|
25
|
+
|
|
26
|
+
Every coding agent working on Webspresso **MUST** follow this 4-step execution discipline:
|
|
27
|
+
|
|
28
|
+
1. **Discover & Triage**: Identify the relevant topic guide above and inspect existing similar files in the workspace (e.g. `pages/api/`, `models/`, `services/`).
|
|
29
|
+
2. **Contract Adherence**: Follow the exact module contract exported by the framework (`{ schema, middleware, handler }` for routes, `defineModel` for ORM, `defineService` for services).
|
|
30
|
+
3. **Zero External Dependencies**: Use native Node.js standard modules (`crypto`, `path`, `fs`, `zlib`, `events`) and built-in framework utilities. NEVER install ad-hoc npm packages for auth, jwt, routing, or templating.
|
|
31
|
+
4. **Targeted Verification**: Run the specific Vitest test file (`npx vitest run tests/...`) to confirm zero regressions before marking the task complete.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## ⚖️ ❌ BAD vs ✅ GOOD Summary Matrix
|
|
36
|
+
|
|
37
|
+
| Category | ❌ NEVER DO THIS (Anti-Pattern) | ✅ ALWAYS DO THIS (Webspresso Way) |
|
|
38
|
+
| :--- | :--- | :--- |
|
|
39
|
+
| **Routing** | `routes/index.js`, `express.Router()`, `app.get('/api/users')` | `pages/api/users.get.js` exporting `{ schema, handler }` |
|
|
40
|
+
| **Database** | `db('users').where({ id }).select()` (Raw Knex queries) | `db.getRepository('User').findById(id)` |
|
|
41
|
+
| **Business Logic** | `controllers/UserController.js` | `services/user/get.js` → `ctx.service('user.get', input)` |
|
|
42
|
+
| **Auth** | `npm i jsonwebtoken passport express-session` | Built-in `req.auth`, `core/auth`, `middleware: ['jwt']` |
|
|
43
|
+
| **SSR Rendering**| `res.render('profile.njk', data)` inside custom handlers | Companion loader `pages/profile.js` exporting `async function load()` |
|
|
44
|
+
| **Exceptions** | `res.status(404).json({ error: 'Not found' })` | `throw new NotFoundError('User not found')` |
|
|
45
|
+
| **Background** | `setTimeout(...)`, `npm i bullmq` | `jobs/user/welcome.js` → `req.queue.dispatch('user.welcome', data)` |
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 1. Codebase Architecture
|
|
50
|
+
|
|
51
|
+
### 1.1 Core SSR & Routing (`pages/`, `views/`)
|
|
52
|
+
- **File-based Routing**: Pages located in `pages/` automatically map to URL routes (e.g. `pages/index.njk` → `/`, `pages/products/[id].njk` → `/products/:id`).
|
|
53
|
+
- **Data Loaders (`load()`)**: Page routes export `async function load({ req, res, db, ctx })` to fetch data server-side before template rendering.
|
|
54
|
+
- **Templating & Helpers**: Nunjucks (`.njk`) templates rendered with layouts (e.g. `views/layout.njk`). Access helpers via `fsy` object in templates.
|
|
55
|
+
- **Asset Versioning & Cache-Busting**: Configured via `createApp({ assets: { version, manifestPath, prefix } })`. Supports query-string versioning (`?v=1.2.3`), Vite/Webpack manifest resolution (`.vite/manifest.json`), CDN prefixing, and `fsy.asset()`, `fsy.css()`, `fsy.js()`, `fsy.img()` template helpers.
|
|
56
|
+
- **Framework Exceptions & Central Error Boundary**: Built-in exception hierarchy (`WebspressoError`, `HttpError`, `ValidationError`, `SecurityError`, `RequestAbortedError`, `ConfigurationError`, `PluginError`, `RouteNotFoundError`) and custom handler hook (`app.setErrorHandler`). Async route handlers automatically propagate rejected promises. Production responses mask 500 stack traces and error details.
|
|
57
|
+
- **Graceful Shutdown & Lifecycle Management**: Configured via `createApp({ server: { shutdown: { enabled: true, mode: 'graceful'|'force', timeout: 10000 } } })`. Socket connection tracking, request draining with `Connection: close`, and reverse-order plugin disposer cleanup.
|
|
58
|
+
- **HTTP Response Compression**: Zero-dependency streaming zlib compression configured via `createApp({ server: { compression: true } })` with Brotli (`br`), Gzip (`gzip`), Deflate (`deflate`), threshold buffering (1 KB), and route-level opt-out (`res.compress(false)`).
|
|
59
|
+
- **Custom Error & 404 Pages**: Configured via `createApp({ errorPages: { notFound, serverError, timeout } })`. Supports template paths (e.g. `notFound: '404.njk'`) or custom handler functions, passing `{ fsy, locale, isDev, url, method }` to templates.
|
|
60
|
+
- **Middleware & Hooks**: Route lifecycle hooks can be declared per-page or globally via `pages/_hooks.js`.
|
|
61
|
+
- **API Endpoints & Routing Architecture**:
|
|
62
|
+
- ⚠️ **CRITICAL ANTI-PATTERN: NEVER create a `routes/` or `src/routes/` directory. NEVER use `express.Router()`, `app.get()`, or `app.post()` for application endpoints.**
|
|
63
|
+
- **100% File-Based Routing**: All API routes MUST be created inside `pages/api/...` with HTTP method extensions:
|
|
64
|
+
- `pages/api/notes.get.js` → `GET /api/notes`
|
|
65
|
+
- `pages/api/notes.post.js` → `POST /api/notes`
|
|
66
|
+
- `pages/api/notes/[id].get.js` → `GET /api/notes/:id`
|
|
67
|
+
- `pages/api/notes/[id].delete.js` → `DELETE /api/notes/:id`
|
|
68
|
+
- **Standard API Route Module Contract**:
|
|
69
|
+
```javascript
|
|
70
|
+
const { defineApi } = require('webspresso');
|
|
71
|
+
|
|
72
|
+
module.exports = defineApi({
|
|
73
|
+
schema: ({ z }) => ({
|
|
74
|
+
body: z.object({ title: z.string().min(1) }),
|
|
75
|
+
params: z.object({ id: z.string().optional() }),
|
|
76
|
+
}),
|
|
77
|
+
middleware: ['auth'], // optional named or functional middleware
|
|
78
|
+
handler: async (req, res, ctx) => {
|
|
79
|
+
// req.db and req.service are automatically injected
|
|
80
|
+
// req.input.body contains the validated payload
|
|
81
|
+
const item = await req.db.getRepository('Note').create(req.input.body);
|
|
82
|
+
return res.status(201).json(item);
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
- **Modular Architecture (`src/modules/` or `modules/`)**:
|
|
87
|
+
- Encapsulate domain features using `defineModule({ name, pages, api, services, middlewares })`.
|
|
88
|
+
- Auto-registers routes (`/auth/login`, `/api/auth/login`), services (`'auth.login'`), and scoped middlewares without manual router glue.
|
|
89
|
+
|
|
90
|
+
### 1.2 Services Layer (`services/`, `src/services/`)
|
|
91
|
+
- **File-based Auto-Discovery**: Services located in `services/` map directly to dot-separated service names (e.g. `services/user/create.js` → `'user.create'`) and camelCase aliases (`'user.reset-password'` & `'user.resetPassword'`).
|
|
92
|
+
- **Declarative Validation & Authorization**: Define optional Zod `schema`, `auth` (boolean, role string/array, predicate), `timeout` (ms), `transaction: true` (ACID transaction propagation across nested calls), and `cache: { ttl, key }`.
|
|
93
|
+
- **Unified Invocation**: Consumed via `ctx.service('service.name', input)` from SSR loaders, `req.service()` in API routes, or standalone `createServiceRegistry()`.
|
|
94
|
+
|
|
95
|
+
### 1.3 ORM & Database Layer (`core/orm`, `models/`)
|
|
96
|
+
- ⚠️ **CRITICAL ANTI-PATTERN: NEVER use raw Knex queries (e.g. `db('table')` or `knex('table')`) in application code.** Raw queries bypass schema validation, lifecycle hooks, soft-deletes, multi-tenant scopes, hidden fields, and query caching.
|
|
97
|
+
- **ALWAYS Use Repositories**: Accessed via `db.getRepository('ModelName')` or `req.db.getRepository('ModelName')`:
|
|
98
|
+
- `await repo.findById(id)`
|
|
99
|
+
- `await repo.find({ status: 'active' })`
|
|
100
|
+
- `await repo.findOne({ slug })`
|
|
101
|
+
- `await repo.create(data)` *(validates schema & executes hooks)*
|
|
102
|
+
- `await repo.update(id, data)`
|
|
103
|
+
- `await repo.delete(id)` *(respects soft deletes)*
|
|
104
|
+
- `await repo.query().where('price', '>', 50).paginate({ page, limit })`
|
|
105
|
+
- `await repo.query().include('user', 'tags').find()` *(eager loading)*
|
|
106
|
+
- **Model Definition**: Defined in `models/*.js` using `defineModel({ name, table, schema, relations, scopes, hidden, admin, cache })`.
|
|
107
|
+
- **Schema & Types**: Built with `zdb` schema builder (`zdb.id()`, `zdb.string()`, `zdb.integer()`, `zdb.boolean()`, `zdb.file()`, `zdb.json()`).
|
|
108
|
+
- **Ambient Transactions**: Repositories automatically bind to ambient transactions (`AsyncLocalStorage`) inside `db.transaction()` and services (`transaction: true`) without manual `trx` passing.
|
|
109
|
+
- **Query Caching**: Memory/provider query cache configured per database instance or model (`cache: 'auto'|'smart'|true`).
|
|
110
|
+
- **Migrations**: Database schema changes managed via Knex migrations in `migrations/` (`webspresso db:migrate`).
|
|
111
|
+
|
|
112
|
+
### 1.4 Admin Panel Architecture (`plugins/admin-panel`)
|
|
113
|
+
- **SPA CRUD Engine**: Modular Mithril.js SPA mounted at `/_admin` powered by `adminPanelPlugin({ db })`.
|
|
114
|
+
- **Admin Module Registration**: Registered using `adminApi.registerModule` or `adminApi.registerPageDir`.
|
|
115
|
+
- **Custom Admin Pages**:
|
|
116
|
+
- `componentFile`: Path to a clean Mithril component `.js` file.
|
|
117
|
+
- `pagesDir`: Directory auto-discovery containing page folders with `page.json` and `component.js`.
|
|
118
|
+
- **Auto Layout Wrapping**: By default (`layout: true`), custom page views are automatically wrapped inside the Admin `Layout` and `Breadcrumb` containers without requiring manual `m(Layout, ...)` nesting. Set `layout: false` for full-screen / custom canvas pages.
|
|
119
|
+
- **Extension Points**:
|
|
120
|
+
- Custom Field Renderers: `registry.registerFieldRenderer(type, { display, edit })`.
|
|
121
|
+
- Widgets: `registry.registerWidget(id, config)`.
|
|
122
|
+
- Actions & Bulk Actions: Single record buttons (`registerAction`) and multi-select table actions (`registerBulkAction`).
|
|
123
|
+
- **Session Isolation**: Admin staff authentication uses a separate session (`req.session.adminUser` / `/_admin/api/auth/*`), isolated from public site auth (`req.user`).
|
|
124
|
+
|
|
125
|
+
### 1.5 Plugin Ecosystem (`plugins/`)
|
|
126
|
+
Plugins expose `name`, `version`, `dependencies`, and lifecycle hooks (`register(ctx)`, `onRoutesReady(ctx)`):
|
|
127
|
+
- `adminPanelPlugin`: Model CRUD, custom pages, widgets, user management.
|
|
128
|
+
- `fileManagerPlugin`: Media library, directory tree, file preview, and drag-and-drop uploads.
|
|
129
|
+
- `basicAuthPlugin`: Zero-dependency HTTP Basic Authentication (RFC 7617) with named route middleware (`ctx.middlewares.basicAuth`) and global protection.
|
|
130
|
+
- `contentPlugin`: Schema-driven CMS with inline editing and headless API.
|
|
131
|
+
- `uploadPlugin`: Multipart file uploads with local/cloud storage providers.
|
|
132
|
+
- `redirectPlugin`: Configurable HTTP 301–308 redirects.
|
|
133
|
+
- `rateLimitPlugin`: Named rate limiters (`express-rate-limit`).
|
|
134
|
+
- `siteAnalyticsPlugin`: Self-hosted page view analytics and client JS error tracking.
|
|
135
|
+
- `emailPlugin`: MJML template compilation & Nodemailer integration.
|
|
136
|
+
- `polarPlugin`: Polar.sh subscription billing (checkout, portal, webhooks, Customer State sync). Requires `rateLimitPlugin` before polar for rate limiters. Supports `hooks.isPaidUser`, `syncBeforeCheckout`, `syncBillingForAppUser`. See `docs/guides/polar-production-checklist.md`.
|
|
137
|
+
- `auditLogPlugin`: Admin mutation audit logging.
|
|
138
|
+
- `ormCacheAdminPlugin`: Cache inspection and invalidation dashboard.
|
|
139
|
+
- `corsPlugin`: Zero-dependency Cross-Origin Resource Sharing handler.
|
|
140
|
+
- `csrfPlugin`: CSRF token validation and Nunjucks form helpers.
|
|
141
|
+
- `realtimePlugin`: Framework-agnostic, plugin-based realtime layer (`core/realtime`) with WebSocket, SSE, Socket.IO, and distributed Redis Pub/Sub adapters.
|
|
142
|
+
- `restResourcePlugin`: Zero-boilerplate RESTful CRUD API generator directly from ORM models with eager loading, sanitization, filtering, pagination, and soft-delete scoping.
|
|
143
|
+
- `xlsxPlugin`: Excel spreadsheet generation, parsing, ORM streaming export (`res.xlsx`), and built-in services (`xlsx.generate`, `xlsx.parse`, `xlsx.exportModel`).
|
|
144
|
+
- `csvPlugin`: RFC 4180 CSV generator, parser, Excel UTF-8 BOM compatibility, formula sanitization, streaming writer (`res.csv`), and built-in services (`csv.generate`, `csv.parse`, `csv.exportModel`).
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## 2. Development & Testing Workflow
|
|
149
|
+
|
|
150
|
+
### 2.1 Essential Commands
|
|
151
|
+
- **Dev Server**: `npm run dev` or `webspresso dev`
|
|
152
|
+
- **Run Vitest Unit Tests**: `npm test` or `npx vitest run tests/unit/admin-panel/`
|
|
153
|
+
- **Run Migrations**: `npx webspresso db:migrate`
|
|
154
|
+
- **Project Sanity Check**: `npx webspresso doctor`
|
|
155
|
+
- **Scaffold AI Agent Rules**: `npx webspresso agents:init`
|
|
156
|
+
|
|
157
|
+
### 2.2 Testing & Quality Assurance
|
|
158
|
+
- **Zero Regression Policy**: Always run unit tests before declaring any task complete.
|
|
159
|
+
- **Targeted Test Execution**: Run specific unit test files during development for fast feedback (`npx vitest run tests/unit/admin-panel/admin-module-files.test.js`).
|
|
160
|
+
- **API Backward Compatibility**: Never break existing `registerModule`, `registerPage`, or `adminApi` contracts. Always add new parameters as optional or backward-compatible enhancements.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 3. Coding Standards & Conventions
|
|
165
|
+
|
|
166
|
+
1. **Strict Signature & Schema Verification**: Always verify function signatures and object schemas by viewing authoritative source files before calling or extending them.
|
|
167
|
+
2. **File & Component Separation**: Keep client-side components in clean standalone JS files (`component.js` / `componentFile`) rather than escaping inline template strings.
|
|
168
|
+
3. **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.
|
|
169
|
+
4. **Strict File-Based Routing (NO `routes/` or `app.get()`)**: Webspresso is strictly file-based. Never create manual router files (`src/routes/*.js`), never call `app.get()`, and never use `express.Router()`. All application pages belong in `pages/` and all API endpoints belong in `pages/api/` with HTTP method extensions (`.get.js`, `.post.js`, `.delete.js`, `.patch.js`, `.put.js`).
|
|
170
|
+
5. **Strict Repository Pattern (NO Raw `db('table')`)**: Never write raw Knex table queries in route handlers or services. Always use `req.db.getRepository('ModelName')` to ensure schema validation, hooks, scopes, and query caching are applied.
|
|
171
|
+
6. **Strict Services Layer (NO `controllers/`)**: Never create a `controllers/` folder or controller classes. Encapsulate business logic in `services/domain/action.js` and call via `ctx.service('domain.action', input)`.
|
|
172
|
+
7. **Native Dual Auth (NO External JWT/Passport Packages)**: Never install `jsonwebtoken`, `passport`, or `express-session`. Always use native `req.auth` and `webspresso/core/auth`.
|
|
173
|
+
8. **Page Data Loaders (NO Manual `res.render()`)**: Never call `res.render()` inside custom handlers for web pages. Use companion loaders `pages/*.js` exporting `async function load({ req, res, db, ctx })`.
|
|
174
|
+
9. **Semantic Exceptions (NO Generic Errors / Manual Responses)**: Never return manual `res.status(404).json(...)` inside services or loaders. Always throw semantic errors: `NotFoundError`, `ValidationError`, `UnauthorizedError`, `ForbiddenError`.
|
|
175
|
+
10. **Background Jobs (NO BullMQ / `setTimeout`)**: Never use unmanaged `setTimeout` or external queues. Place job workers in `jobs/` and dispatch via `req.queue.dispatch()`.
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Webspresso — Claude Code Assistant Guide
|
|
2
|
+
|
|
3
|
+
Webspresso is a lightweight, zero-dependency-sprawl full-stack Node.js SSR & API framework.
|
|
4
|
+
|
|
5
|
+
> [!IMPORTANT]
|
|
6
|
+
> **Authoritative Rules & Architecture**: Always follow [AGENTS.md](AGENTS.md) and the modular topic guides in [.agents/](.agents/AGENTS.md).
|
|
7
|
+
> **API & Types Ground Truth**: Inspect [index.d.ts](index.d.ts) for exact method signatures.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## ⚡ Essential Commands
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Run test suite (Node >= 20 required)
|
|
15
|
+
npm test
|
|
16
|
+
npx vitest run tests/unit/
|
|
17
|
+
npx vitest run tests/integration/
|
|
18
|
+
npx vitest run tests/security/
|
|
19
|
+
|
|
20
|
+
# Development & diagnostics
|
|
21
|
+
npm run dev
|
|
22
|
+
npx webspresso doctor
|
|
23
|
+
npx webspresso db:migrate
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 🛡️ Critical Architecture Rules
|
|
29
|
+
|
|
30
|
+
1. **100% File-Based Routing (NO `routes/` or `app.get()`)**:
|
|
31
|
+
- All endpoints live in `pages/api/...` with HTTP method extensions:
|
|
32
|
+
- `pages/api/notes.get.js` → `GET /api/notes`
|
|
33
|
+
- `pages/api/notes.post.js` → `POST /api/notes`
|
|
34
|
+
- `pages/api/notes/[id].get.js` → `GET /api/notes/:id`
|
|
35
|
+
- `pages/api/notes/[id].delete.js` → `DELETE /api/notes/:id`
|
|
36
|
+
- Contract: `module.exports = { schema: ({ z }) => ({ body, query, params }), middleware: ['auth'], handler: async (req, res) => ... }`
|
|
37
|
+
|
|
38
|
+
2. **Strict Repository Pattern (NO Raw `db('table')`)**:
|
|
39
|
+
- Never run raw Knex queries (`req.db('table')`).
|
|
40
|
+
- Always use `req.db.getRepository('ModelName')` to ensure schema validation, hooks, scopes, and query caching.
|
|
41
|
+
|
|
42
|
+
3. **Services Layer (NO `controllers/`)**:
|
|
43
|
+
- Place business logic in `services/domain/action.js` and invoke via `ctx.service('domain.action', input)`.
|
|
44
|
+
|
|
45
|
+
4. **Zero-Dependency Native Dual Auth**:
|
|
46
|
+
- Never install `jsonwebtoken`, `passport`, or `express-session`.
|
|
47
|
+
- Use built-in `req.auth` and `webspresso/core/auth`.
|
|
48
|
+
|
|
49
|
+
5. **Semantic Exceptions & Page Loaders**:
|
|
50
|
+
- Throw `NotFoundError`, `ValidationError`, `UnauthorizedError`, `ForbiddenError`.
|
|
51
|
+
- For SSR pages (`pages/name.njk`), use companion loaders `pages/name.js` exporting `async function load({ req, res, db, ctx })`. Never call `res.render()` manually.
|
|
52
|
+
|
|
53
|
+
6. **Zero Regression Policy**:
|
|
54
|
+
- Always run the relevant Vitest tests before declaring any task complete.
|
package/README.md
CHANGED
|
@@ -28,7 +28,9 @@ Open **`http://localhost:3000`** in your browser.
|
|
|
28
28
|
## ✨ Why Webspresso?
|
|
29
29
|
|
|
30
30
|
- **Zero-Dependency Sprawl**: Core framework features (Dual Auth, JWT signing, Error handling, Streaming Compression, Graceful Shutdown, Template Helpers) rely strictly on native Node.js standard modules (`crypto`, `path`, `fs`, `events`, `zlib`, `async_hooks`) without bloated dependency trees.
|
|
31
|
-
- **File-Based Routing**:
|
|
31
|
+
- **File-Based Routing & API Suffixes**: Pages and APIs automatically map to clean URLs with dynamic params (`[id]`), wildcards (`[...slug]`), and method suffixes (`.get.js`, `.post.js`, `.delete.js`, `.patch.js`, `.put.js`).
|
|
32
|
+
- **Feature Modules (`defineModule`)**: Domain-driven modular organization for large projects (`modules/{name}/pages`, `api`, `services`, `middleware`).
|
|
33
|
+
- **Modern Page & API Helpers (`definePage`, `defineApi`)**: Declarative Zod request schemas, unified execution context, and automatic JSON response serialization.
|
|
32
34
|
- **Server Data Prefetching (`load()`)**: Page routes export `async function load({ req, res, db, ctx })` to fetch data server-side before template compilation.
|
|
33
35
|
- **Services Layer**: File-based auto-discovery in `services/` with declarative Zod validation, role/predicate authorization guards, and memoization.
|
|
34
36
|
- **Ambient Transactions**: Knex database transactions automatically bind to `AsyncLocalStorage` (`core/orm/transaction.js`). Scoped repositories seamlessly participate in active transactions without manual `trx` passing.
|
|
@@ -78,33 +80,35 @@ app.listen(PORT, () => {
|
|
|
78
80
|
|
|
79
81
|
```javascript
|
|
80
82
|
// pages/index.js
|
|
81
|
-
|
|
83
|
+
const { definePage } = require('webspresso');
|
|
84
|
+
|
|
85
|
+
module.exports = definePage({
|
|
82
86
|
async load({ req, ctx }) {
|
|
83
87
|
return {
|
|
84
88
|
title: 'Home Page',
|
|
85
89
|
user: req.user || null,
|
|
86
90
|
};
|
|
87
91
|
},
|
|
88
|
-
};
|
|
92
|
+
});
|
|
89
93
|
```
|
|
90
94
|
|
|
91
95
|
### 3. Validated JSON API Endpoint (`pages/api/greet.post.js`)
|
|
92
96
|
```javascript
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
97
|
+
const { defineApi, z } = require('webspresso');
|
|
98
|
+
|
|
99
|
+
module.exports = defineApi({
|
|
100
|
+
schema: {
|
|
101
|
+
body: z.object({
|
|
102
|
+
name: z.string().min(2),
|
|
103
|
+
}),
|
|
100
104
|
},
|
|
101
105
|
|
|
102
|
-
async handler(req, res) {
|
|
106
|
+
async handler(req, res, ctx) {
|
|
103
107
|
// req.input is typed and validated by Zod
|
|
104
|
-
const result = await
|
|
105
|
-
|
|
108
|
+
const result = await ctx.service('user.greet', req.input.body);
|
|
109
|
+
return result; // Automatically serialized as JSON (HTTP 200)
|
|
106
110
|
},
|
|
107
|
-
};
|
|
111
|
+
});
|
|
108
112
|
```
|
|
109
113
|
|
|
110
114
|
---
|
|
@@ -92,7 +92,8 @@ function registerCommand(program) {
|
|
|
92
92
|
|
|
93
93
|
// Hash the password (same rounds as admin panel setup)
|
|
94
94
|
const { hash } = require('../../core/auth/hash');
|
|
95
|
-
const
|
|
95
|
+
const rounds = process.env.NODE_ENV === 'test' ? 4 : 10;
|
|
96
|
+
const hashedPassword = await hash(password, rounds);
|
|
96
97
|
|
|
97
98
|
// Update the password
|
|
98
99
|
await db('admin_users')
|