webspresso 0.0.108 → 0.0.110
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/LICENSE +21 -0
- package/README.md +3 -3
- package/bin/commands/add-esbuild.js +7 -3
- package/bin/commands/add-tailwind.js +9 -5
- package/bin/commands/build.js +20 -4
- package/bin/commands/dev.js +57 -7
- package/bin/commands/doctor.js +42 -1
- package/bin/commands/favicon-generate.js +10 -3
- package/bin/commands/new.js +8 -3
- package/bin/commands/start.js +3 -1
- package/bin/utils/add-command.js +16 -0
- package/core/app.js +45 -3
- package/core/auth/authenticate.js +3 -3
- package/core/auth/hash.js +167 -43
- package/core/auth/index.js +13 -1
- package/core/auth/jwt.js +21 -2
- package/core/auth/manager.js +63 -14
- package/core/auth/middleware.js +4 -2
- package/core/auth/oauth.js +5 -1
- package/core/auth/tokens.js +7 -2
- package/core/auth/totp.js +42 -6
- package/core/auth/user-agent.js +2 -0
- package/core/client/adapter/datastar.js +1401 -0
- package/core/client/api.js +1337 -0
- package/core/client/controller.js +1706 -0
- package/core/client/debug.js +367 -0
- package/core/client/devtool.js +1134 -0
- package/core/client/forms.js +1164 -0
- package/core/client/fragments.js +894 -0
- package/core/client/index.d.ts +59 -0
- package/core/client/index.js +391 -0
- package/core/client/lite.js +175 -0
- package/core/client/server-fragments.js +868 -0
- package/core/client/storage.js +339 -0
- package/core/client/utils.js +281 -0
- package/core/compression/middleware.js +277 -26
- package/core/context/app-context.js +25 -3
- package/core/context/request.js +32 -4
- package/core/errors/middleware.js +14 -0
- package/core/events/dispatcher.js +33 -0
- package/core/forms/flash.js +136 -102
- package/core/health/middleware.js +36 -6
- package/core/http/pipeline.js +45 -6
- package/core/index.js +25 -7
- package/core/kernel/view.js +41 -10
- package/core/metrics/middleware.js +30 -6
- package/core/metrics/registry.js +13 -5
- package/core/orm/cache/fingerprint.js +32 -11
- package/core/orm/cache/layer.js +19 -2
- package/core/orm/hidden.js +145 -0
- package/core/orm/model.js +48 -2
- package/core/orm/query-builder.js +343 -6
- package/core/orm/query-filters.js +14 -2
- package/core/orm/repository.js +64 -13
- package/core/orm/schema-helpers.js +9 -6
- package/core/orm/transaction.js +50 -6
- package/core/orm/types.js +4 -0
- package/core/queue/adapters/database.js +184 -2
- package/core/queue/adapters/memory.js +153 -5
- package/core/queue/adapters/redis.js +59 -4
- package/core/queue/job.js +14 -1
- package/core/queue/manager.js +115 -0
- package/core/resilience/circuit-breaker.js +27 -8
- package/core/services/builtins/exchange.js +174 -7
- package/core/services/builtins/file-manager.js +36 -0
- package/core/services/builtins/mail.js +27 -0
- package/core/services/builtins/queue.js +144 -0
- package/core/services/builtins/security.js +118 -0
- package/core/services/builtins/webhook.js +110 -0
- package/core/services/executor.js +38 -16
- package/core/services/index.js +4 -1
- package/core/sse/stream.js +28 -5
- package/core/utils/timeout.js +8 -1
- package/core/validation/compiler.js +10 -7
- package/index.d.ts +1320 -74
- package/index.js +128 -8
- package/package.json +75 -27
- package/plugins/admin-panel/client/parts/08-record-list.js +717 -70
- package/plugins/admin-panel/index.js +19 -8
- package/plugins/csrf/index.js +3 -3
- package/plugins/data-exchange/export.js +214 -0
- package/plugins/data-exchange/import.js +48 -5
- package/plugins/data-exchange/index.js +70 -28
- package/plugins/data-exchange/preview.js +266 -0
- package/plugins/data-exchange/template.js +242 -0
- package/plugins/email/admin-component.js +783 -141
- package/plugins/email/api-handlers.js +51 -1
- package/plugins/email/index.js +5 -0
- package/plugins/email/service.js +48 -0
- package/plugins/file-manager/admin-component.js +1546 -70
- package/plugins/file-manager/api-handlers.js +166 -1
- package/plugins/file-manager/core.js +598 -8
- package/plugins/file-manager/index.js +8 -0
- package/plugins/index.js +7 -0
- package/plugins/mcp/auth.js +3 -3
- package/plugins/oauth/index.js +36 -3
- package/plugins/queue/admin-component.js +1092 -0
- package/plugins/queue/api-handlers.js +248 -0
- package/plugins/queue/index.js +69 -0
- package/plugins/rest-resources/index.js +7 -2
- package/plugins/security/admin-component.js +689 -0
- package/plugins/security/api-handlers.js +169 -0
- package/plugins/security/audit.js +153 -0
- package/plugins/security/bruteforce.js +244 -0
- package/plugins/security/firewall.js +459 -0
- package/plugins/security/headers.js +214 -0
- package/plugins/security/honeypot.js +234 -0
- package/plugins/security/incident-recorder.js +137 -0
- package/plugins/security/index.js +211 -0
- package/plugins/seo-checker/analyzer.js +20 -7
- package/plugins/upload/index.js +36 -0
- package/plugins/webhooks/admin-component.js +702 -175
- package/plugins/webhooks/api-handlers.js +75 -1
- package/plugins/webhooks/dispatcher.js +290 -11
- package/plugins/webhooks/index.js +37 -2
- package/plugins/webhooks/sender.js +56 -0
- package/plugins/xlsx/engine.js +19 -4
- package/src/api/api-loader.js +19 -1
- package/src/bundle/index.js +443 -17
- package/src/client-runtime/bootstrap-swup.js +9 -0
- package/src/client-runtime/mount.js +15 -20
- package/src/client-runtime/resolve.js +3 -2
- package/src/dev/api-typegen.js +1300 -0
- package/src/dev/banner.js +10 -1
- package/src/dev/colors.js +1 -1
- package/src/dev/index.js +7 -1
- package/src/dev/typegen.js +173 -17
- package/src/dev-reload/index.js +1 -1
- package/src/discovery/file-route-parser.js +17 -3
- package/src/discovery/scan-routes.js +4 -1
- package/src/file-router.js +253 -69
- package/src/helpers.js +46 -26
- package/src/njk-frontmatter.js +30 -4
- package/src/routing/cascading.js +26 -8
- package/src/routing/mount-discovered-routes.js +76 -0
- package/src/server.js +231 -70
- package/views/partials/webspresso-client-runtime.njk +1 -9
- package/.agents/ADMIN.md +0 -426
- package/.agents/AGENTS.md +0 -96
- package/.agents/ARCHITECTURE.md +0 -163
- package/.agents/AUTH.md +0 -581
- package/.agents/CACHE.md +0 -148
- package/.agents/CLI.md +0 -649
- package/.agents/COMPRESSION.md +0 -59
- package/.agents/CONTEXT.md +0 -152
- package/.agents/CONVENTIONS.md +0 -50
- package/.agents/EARLY_HINTS.md +0 -120
- package/.agents/EMAIL.md +0 -223
- package/.agents/ERRORS.md +0 -260
- package/.agents/EVENTS.md +0 -179
- package/.agents/FLAGS.md +0 -118
- package/.agents/HEALTH.md +0 -118
- package/.agents/LIMITS.md +0 -109
- package/.agents/LOGGING.md +0 -93
- package/.agents/MCP.md +0 -103
- package/.agents/METRICS.md +0 -127
- package/.agents/MIGRATION_JOBS.md +0 -95
- package/.agents/ORM.md +0 -700
- package/.agents/PLUGINS.md +0 -729
- package/.agents/QUEUE.md +0 -290
- package/.agents/RATE_LIMIT.md +0 -147
- package/.agents/REALTIME.md +0 -213
- package/.agents/RESILIENCE.md +0 -235
- package/.agents/ROUTING.md +0 -605
- package/.agents/SCALE_OUT.md +0 -109
- package/.agents/SECRETS.md +0 -117
- package/.agents/SERVICES.md +0 -341
- package/.agents/SHUTDOWN.md +0 -74
- package/.agents/STREAMING.md +0 -108
- package/.agents/WORKFLOW.md +0 -27
- package/.agents/internal/DOCUMENTATION_REVIEW.md +0 -485
- package/.agents/internal/TECHNICAL_REVIEW.md +0 -428
- package/.agents/skills/webspresso-admin-and-plugins/SKILL.md +0 -122
- package/.agents/skills/webspresso-framework/SKILL.md +0 -89
- package/.agents/skills/webspresso-orm-and-database/SKILL.md +0 -127
- package/.agents/skills/webspresso-routing-and-api/SKILL.md +0 -130
- package/.agents/skills/webspresso-services/SKILL.md +0 -134
- package/.cursorrules +0 -14
- package/AGENTS.md +0 -203
- package/CLAUDE.md +0 -54
- package/core/kernel/run-demo.js +0 -58
- package/plugins/admin-panel/styles.js +0 -9
- package/plugins/data-exchange/export-xlsx.js +0 -119
- package/src/client-runtime/bootstrap-alpine-swup.js +0 -34
package/CLAUDE.md
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
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/core/kernel/run-demo.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Demo: PostRepository + plugin + flow. Run: node core/kernel/run-demo.js
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const { createApp, defineFlow, BaseRepository } = require('./app');
|
|
7
|
-
const sampleSeo = require('./plugins/sample-seo');
|
|
8
|
-
|
|
9
|
-
class PostRepository extends BaseRepository {
|
|
10
|
-
constructor(events) {
|
|
11
|
-
super(events, { resource: 'post', source: 'orm' });
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async function main() {
|
|
16
|
-
const app = createApp();
|
|
17
|
-
|
|
18
|
-
app.events.on('orm.post.beforeCreate', async () => {
|
|
19
|
-
console.log('[beforeCreate]');
|
|
20
|
-
});
|
|
21
|
-
app.events.on('orm.post.afterCreate', async () => {
|
|
22
|
-
console.log('[afterCreate]');
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
app.registerPlugin(sampleSeo);
|
|
26
|
-
|
|
27
|
-
app.registerFlow(
|
|
28
|
-
defineFlow({
|
|
29
|
-
id: 'sitemap-on-publish',
|
|
30
|
-
trigger: 'orm.post.afterCreate',
|
|
31
|
-
when: (ctx) => ctx.payload.record?.status === 'published',
|
|
32
|
-
actions: [
|
|
33
|
-
async () => {
|
|
34
|
-
console.log('[flow] Run sitemap update');
|
|
35
|
-
},
|
|
36
|
-
],
|
|
37
|
-
}),
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
const posts = new PostRepository(app.events);
|
|
41
|
-
|
|
42
|
-
console.log('--- create post (published) ---');
|
|
43
|
-
await posts.create({ title: 'Hello', status: 'published' });
|
|
44
|
-
|
|
45
|
-
console.log('\n--- view render (inline plugin template) ---');
|
|
46
|
-
const html = app.view.renderView('seo::home', { title: 'Kernel' }, {
|
|
47
|
-
layout: 'seo::main',
|
|
48
|
-
});
|
|
49
|
-
console.log(html.slice(0, 120).replace(/\s+/g, ' ') + '...');
|
|
50
|
-
|
|
51
|
-
console.log('\n--- partial ---');
|
|
52
|
-
console.log(app.view.renderPartial('seo::badge', { label: 'new' }));
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
main().catch((err) => {
|
|
56
|
-
console.error(err);
|
|
57
|
-
process.exit(1);
|
|
58
|
-
});
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Excel (.xlsx) export using exceljs
|
|
3
|
-
* @module plugins/data-exchange/export-xlsx
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
let ExcelJS = null;
|
|
7
|
-
function getExcelJS() {
|
|
8
|
-
if (!ExcelJS) {
|
|
9
|
-
ExcelJS = require('exceljs');
|
|
10
|
-
}
|
|
11
|
-
return ExcelJS;
|
|
12
|
-
}
|
|
13
|
-
const { sanitizeForOutput } = require('../../core/orm/utils');
|
|
14
|
-
const { resolveExportRecords } = require('./record-selection');
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @param {import('../../core/orm/types').ModelDefinition} model
|
|
18
|
-
* @param {object[]} records
|
|
19
|
-
* @returns {Promise<Buffer>}
|
|
20
|
-
*/
|
|
21
|
-
function getModelExportColumns(model, cleanRecords = []) {
|
|
22
|
-
const hiddenSet = new Set(model.hidden || []);
|
|
23
|
-
const colSet = new Set();
|
|
24
|
-
if (model.columns && typeof model.columns.keys === 'function') {
|
|
25
|
-
for (const c of model.columns.keys()) {
|
|
26
|
-
if (!hiddenSet.has(c)) colSet.add(c);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
if (model.schema?.shape) {
|
|
30
|
-
for (const c of Object.keys(model.schema.shape)) {
|
|
31
|
-
if (!hiddenSet.has(c)) colSet.add(c);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
if (colSet.size === 0 && cleanRecords.length > 0) {
|
|
35
|
-
for (const c of Object.keys(cleanRecords[0])) {
|
|
36
|
-
if (!hiddenSet.has(c)) colSet.add(c);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return Array.from(colSet);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* @param {import('../../core/orm/types').ModelDefinition} model
|
|
44
|
-
* @param {object[]} records
|
|
45
|
-
* @returns {Promise<Buffer>}
|
|
46
|
-
*/
|
|
47
|
-
async function buildXlsxBuffer(model, records) {
|
|
48
|
-
const clean = sanitizeForOutput(records, model);
|
|
49
|
-
const columns = getModelExportColumns(model, clean);
|
|
50
|
-
|
|
51
|
-
const ExcelLib = getExcelJS();
|
|
52
|
-
const wb = new ExcelLib.Workbook();
|
|
53
|
-
const ws = wb.addWorksheet('Export', {
|
|
54
|
-
views: [{ state: 'frozen', ySplit: 1 }],
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
ws.addRow(columns);
|
|
58
|
-
const headerRow = ws.getRow(1);
|
|
59
|
-
headerRow.font = { bold: true };
|
|
60
|
-
|
|
61
|
-
for (const rec of clean) {
|
|
62
|
-
const rowValues = columns.map((c) => {
|
|
63
|
-
let v = rec[c];
|
|
64
|
-
if (v === null || v === undefined) return '';
|
|
65
|
-
if (typeof v === 'bigint') return v.toString();
|
|
66
|
-
if (v instanceof Date) return v.toISOString();
|
|
67
|
-
if (Buffer.isBuffer(v)) return v.toString('base64');
|
|
68
|
-
if (typeof v === 'object') v = JSON.stringify(v);
|
|
69
|
-
if (typeof v === 'string' && /^[=+\-@]/.test(v)) return ` ${v}`;
|
|
70
|
-
return v;
|
|
71
|
-
});
|
|
72
|
-
ws.addRow(rowValues);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const buf = await wb.xlsx.writeBuffer();
|
|
76
|
-
return Buffer.isBuffer(buf) ? buf : Buffer.from(buf);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* @param {object} opts
|
|
81
|
-
* @param {import('../../core/orm/database').Database} opts.db
|
|
82
|
-
* @param {number} opts.maxRows
|
|
83
|
-
*/
|
|
84
|
-
function createExportXlsxHandler(opts) {
|
|
85
|
-
const { db, maxRows } = opts;
|
|
86
|
-
|
|
87
|
-
return async function exportXlsxHandler(req, res) {
|
|
88
|
-
try {
|
|
89
|
-
const modelName = req.params.model || req.query.model;
|
|
90
|
-
const result = await resolveExportRecords(db, modelName, req);
|
|
91
|
-
if (result.error) {
|
|
92
|
-
return res.status(result.error).json({ error: result.message });
|
|
93
|
-
}
|
|
94
|
-
const { model, records } = result;
|
|
95
|
-
if (records.length > maxRows) {
|
|
96
|
-
return res.status(400).json({
|
|
97
|
-
error: `Too many rows to export (${records.length}). Limit is ${maxRows}.`,
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
const buffer = await buildXlsxBuffer(model, records);
|
|
101
|
-
const safeName = String(model.name).replace(/[^\w.-]+/g, '_');
|
|
102
|
-
res.setHeader(
|
|
103
|
-
'Content-Type',
|
|
104
|
-
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
105
|
-
);
|
|
106
|
-
res.setHeader('Content-Disposition', `attachment; filename="${safeName}_export.xlsx"`);
|
|
107
|
-
res.send(buffer);
|
|
108
|
-
} catch (err) {
|
|
109
|
-
console.error('[data-exchange] export xlsx:', err);
|
|
110
|
-
res.status(500).json({ error: err.message || 'Export failed' });
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
module.exports = {
|
|
116
|
-
buildXlsxBuffer,
|
|
117
|
-
createExportXlsxHandler,
|
|
118
|
-
getModelExportColumns,
|
|
119
|
-
};
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/* global Swup, SwupHeadPlugin, SwupScriptsPlugin, Alpine */
|
|
2
|
-
(function () {
|
|
3
|
-
if (typeof Swup === 'undefined' || typeof SwupHeadPlugin === 'undefined' || typeof SwupScriptsPlugin === 'undefined' || typeof Alpine === 'undefined') {
|
|
4
|
-
return;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
function ignoreVisit(url, ctx) {
|
|
8
|
-
var el = ctx && ctx.el;
|
|
9
|
-
if (el && el.closest && el.closest('[data-no-swup]')) return true;
|
|
10
|
-
try {
|
|
11
|
-
var u = new URL(url, window.location.origin);
|
|
12
|
-
var p = u.pathname;
|
|
13
|
-
if (p.indexOf('/_admin') === 0 || p.indexOf('/_webspresso') === 0) return true;
|
|
14
|
-
} catch (e) {
|
|
15
|
-
/* ignore */
|
|
16
|
-
}
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
var swup = new Swup({
|
|
21
|
-
containers: ['#swup'],
|
|
22
|
-
plugins: [new SwupHeadPlugin(), new SwupScriptsPlugin()],
|
|
23
|
-
ignoreVisit: ignoreVisit,
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
swup.hooks.on('content:replace', function () {
|
|
27
|
-
var root = document.querySelector('#swup');
|
|
28
|
-
if (root && typeof Alpine.initTree === 'function') {
|
|
29
|
-
Alpine.initTree(root);
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
window.__webspressoSwup = swup;
|
|
34
|
-
})();
|