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.
Files changed (94) hide show
  1. package/.agents/ADMIN.md +225 -0
  2. package/.agents/AGENTS.md +72 -0
  3. package/.agents/ARCHITECTURE.md +119 -0
  4. package/.agents/AUTH.md +103 -0
  5. package/.agents/CLI.md +115 -0
  6. package/.agents/COMPRESSION.md +59 -0
  7. package/.agents/CONVENTIONS.md +46 -0
  8. package/.agents/EMAIL.md +141 -0
  9. package/.agents/ERRORS.md +201 -0
  10. package/.agents/MCP.md +103 -0
  11. package/.agents/ORM.md +230 -0
  12. package/.agents/PLUGINS.md +419 -0
  13. package/.agents/QUEUE.md +113 -0
  14. package/.agents/REALTIME.md +213 -0
  15. package/.agents/ROUTING.md +292 -0
  16. package/.agents/SERVICES.md +341 -0
  17. package/.agents/SHUTDOWN.md +67 -0
  18. package/.agents/STREAMING.md +73 -0
  19. package/.agents/WORKFLOW.md +27 -0
  20. package/.agents/internal/DOCUMENTATION_REVIEW.md +485 -0
  21. package/.agents/internal/TECHNICAL_REVIEW.md +428 -0
  22. package/.agents/skills/webspresso-admin-and-plugins/SKILL.md +122 -0
  23. package/.agents/skills/webspresso-framework/SKILL.md +56 -0
  24. package/.agents/skills/webspresso-orm-and-database/SKILL.md +119 -0
  25. package/.agents/skills/webspresso-routing-and-api/SKILL.md +114 -0
  26. package/.agents/skills/webspresso-services/SKILL.md +101 -0
  27. package/.cursorrules +14 -0
  28. package/AGENTS.md +175 -0
  29. package/CLAUDE.md +54 -0
  30. package/README.md +18 -14
  31. package/bin/commands/admin-password.js +2 -1
  32. package/bin/commands/admin-setup.js +1 -1
  33. package/bin/commands/agents-init.js +209 -0
  34. package/bin/commands/api.js +114 -48
  35. package/bin/commands/audit-prune.js +1 -1
  36. package/bin/commands/doctor.js +35 -0
  37. package/bin/commands/favicon-generate.js +1 -1
  38. package/bin/commands/new.js +13 -2
  39. package/bin/commands/orm-map.js +5 -4
  40. package/bin/commands/page.js +75 -89
  41. package/bin/commands/polar-migrate.js +1 -1
  42. package/bin/commands/seed.js +1 -1
  43. package/bin/commands/service.js +77 -0
  44. package/bin/webspresso.js +2 -2
  45. package/core/auth/hash.js +10 -5
  46. package/core/compileSchema.js +1 -1
  47. package/core/errors/middleware.js +22 -2
  48. package/core/errors/normalize.js +29 -2
  49. package/{utils/schemaCache.js → core/schema-cache.js} +2 -3
  50. package/index.d.ts +306 -0
  51. package/index.js +35 -1
  52. package/package.json +36 -14
  53. package/plugins/admin-panel/app.js +10 -9
  54. package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +22 -1
  55. package/plugins/admin-panel/core/api-extensions.js +4 -0
  56. package/plugins/basic-auth/index.js +3 -1
  57. package/plugins/content/client/inline-edit.js +12 -12
  58. package/plugins/cors.js +6 -7
  59. package/plugins/csrf/index.js +3 -2
  60. package/plugins/csv/engine.js +447 -0
  61. package/plugins/csv/index.js +94 -0
  62. package/plugins/csv/middleware.js +80 -0
  63. package/plugins/csv/services.js +138 -0
  64. package/plugins/file-manager/admin-component.js +613 -0
  65. package/plugins/file-manager/api-handlers.js +261 -0
  66. package/plugins/file-manager/core.js +601 -0
  67. package/plugins/file-manager/index.js +139 -0
  68. package/plugins/file-manager/security.js +217 -0
  69. package/plugins/index.js +45 -79
  70. package/plugins/mcp/transports/sse.js +4 -2
  71. package/plugins/site-analytics/admin-component.js +7 -8
  72. package/plugins/xlsx/engine.js +397 -0
  73. package/plugins/xlsx/index.js +91 -0
  74. package/plugins/xlsx/middleware.js +87 -0
  75. package/plugins/xlsx/services.js +118 -0
  76. package/src/api/api-loader.js +233 -0
  77. package/src/api/define-api.js +35 -0
  78. package/src/discovery/file-route-parser.js +201 -0
  79. package/src/discovery/scan-routes.js +265 -0
  80. package/src/file-router.js +9 -1
  81. package/src/modules/define-module.js +99 -0
  82. package/src/modules/module-discovery.js +117 -0
  83. package/src/pages/define-page.js +36 -0
  84. package/src/pages/page-loader.js +214 -0
  85. package/src/routing/mount-discovered-routes.js +78 -0
  86. package/src/routing/route-table.js +191 -0
  87. package/src/server.js +112 -15
  88. package/src/services/builtins/file-manager.js +178 -0
  89. package/src/services/discovery.js +6 -1
  90. package/src/services/index.js +6 -11
  91. package/bin/commands/skill.js +0 -187
  92. package/templates/skills/webspresso-usage/REFERENCE-framework.md +0 -289
  93. package/templates/skills/webspresso-usage/REFERENCE-kernel.md +0 -93
  94. 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**: Templates in `pages/` automatically become public URL routes with support for dynamic params (`[id]`) and catch-all wildcards (`[...slug]`).
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
- module.exports = {
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
- module.exports = {
94
- schema({ z }) {
95
- return {
96
- body: z.object({
97
- name: z.string().min(2),
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 req.service('user.greet', req.input.body);
105
- res.json(result);
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 hashedPassword = await hash(password, 10);
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')