webspresso 0.0.4 → 0.0.6

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/README.md CHANGED
@@ -7,11 +7,12 @@ A minimal, file-based SSR framework for Node.js with Nunjucks templating.
7
7
  - **File-Based Routing**: Create pages by adding `.njk` files to a `pages/` directory
8
8
  - **Dynamic Routes**: Use `[param]` for dynamic params and `[...rest]` for catch-all routes
9
9
  - **API Endpoints**: Add `.js` files to `pages/api/` with method suffixes (e.g., `health.get.js`)
10
+ - **Schema Validation**: Zod-based request validation for body, params, and query
10
11
  - **Built-in i18n**: JSON-based translations with automatic locale detection
11
12
  - **Lifecycle Hooks**: Global and route-level hooks for request processing
12
13
  - **Template Helpers**: Laravel-inspired helper functions available in templates
13
14
  - **Plugin System**: Extensible architecture with version control and inter-plugin communication
14
- - **Built-in Plugins**: Sitemap generator, analytics integration (Google, Yandex, Bing)
15
+ - **Built-in Plugins**: Development dashboard, sitemap generator, analytics integration (Google, Yandex, Bing)
15
16
 
16
17
  ## Installation
17
18
 
@@ -325,12 +326,13 @@ Webspresso has a built-in plugin system with version control and dependency mana
325
326
 
326
327
  ```javascript
327
328
  const { createApp } = require('webspresso');
328
- const { sitemapPlugin, analyticsPlugin } = require('webspresso/plugins');
329
+ const { sitemapPlugin, analyticsPlugin, dashboardPlugin } = require('webspresso/plugins');
329
330
 
330
331
  const { app } = createApp({
331
332
  pagesDir: './pages',
332
333
  viewsDir: './views',
333
334
  plugins: [
335
+ dashboardPlugin(), // Dev dashboard at /_webspresso
334
336
  sitemapPlugin({
335
337
  hostname: 'https://example.com',
336
338
  exclude: ['/admin/*', '/api/*'],
@@ -357,6 +359,28 @@ const { app } = createApp({
357
359
 
358
360
  ### Built-in Plugins
359
361
 
362
+ **Dashboard Plugin:**
363
+ - Development dashboard at `/_webspresso`
364
+ - Monitor all routes (SSR pages and API endpoints)
365
+ - View loaded plugins and configuration
366
+ - Filter and search routes
367
+ - Only active in development mode (disabled in production)
368
+
369
+ ```javascript
370
+ const { dashboardPlugin } = require('webspresso/plugins');
371
+
372
+ const { app } = createApp({
373
+ pagesDir: './pages',
374
+ plugins: [
375
+ dashboardPlugin() // Available at /_webspresso in dev mode
376
+ ]
377
+ });
378
+ ```
379
+
380
+ Options:
381
+ - `path` - Custom dashboard path (default: `/_webspresso`)
382
+ - `enabled` - Force enable/disable (default: auto based on NODE_ENV)
383
+
360
384
  **Sitemap Plugin:**
361
385
  - Generates `/sitemap.xml` from routes automatically
362
386
  - Excludes dynamic routes and API endpoints
@@ -471,6 +495,75 @@ Create `.js` files in `pages/api/` with optional method suffixes:
471
495
  | `pages/api/echo.post.js` | `POST /api/echo` |
472
496
  | `pages/api/users/[id].get.js` | `GET /api/users/:id` |
473
497
 
498
+ **Basic API Handler:**
499
+
500
+ ```javascript
501
+ // pages/api/health.get.js
502
+ module.exports = async function handler(req, res) {
503
+ res.json({ status: 'ok' });
504
+ };
505
+ ```
506
+
507
+ **With Schema Validation:**
508
+
509
+ ```javascript
510
+ // pages/api/posts.post.js
511
+ module.exports = {
512
+ schema: ({ z }) => ({
513
+ body: z.object({
514
+ title: z.string().min(3).max(100),
515
+ content: z.string(),
516
+ tags: z.array(z.string()).optional()
517
+ }),
518
+ query: z.object({
519
+ draft: z.coerce.boolean().default(false)
520
+ })
521
+ }),
522
+
523
+ async handler(req, res) {
524
+ // Validated & parsed data available in req.input
525
+ const { title, content, tags } = req.input.body;
526
+ const { draft } = req.input.query;
527
+
528
+ // Original req.body, req.query remain untouched
529
+ res.json({ success: true, title, draft });
530
+ }
531
+ };
532
+ ```
533
+
534
+ **Schema Options:**
535
+
536
+ | Key | Description |
537
+ |-----|-------------|
538
+ | `body` | Validates `req.body` (POST/PUT/PATCH) |
539
+ | `params` | Validates route parameters (e.g., `:id`) |
540
+ | `query` | Validates query string parameters |
541
+ | `response` | Response schema (for documentation, not enforced) |
542
+
543
+ All schemas use [Zod](https://zod.dev) for validation. Invalid requests throw a `ZodError` which can be caught by error handlers.
544
+
545
+ **Dynamic Route with Params Validation:**
546
+
547
+ ```javascript
548
+ // pages/api/users/[id].get.js
549
+ module.exports = {
550
+ schema: ({ z }) => ({
551
+ params: z.object({
552
+ id: z.string().uuid()
553
+ }),
554
+ query: z.object({
555
+ fields: z.string().optional()
556
+ })
557
+ }),
558
+
559
+ async handler(req, res) {
560
+ const { id } = req.input.params; // Validated UUID
561
+ const user = await getUser(id);
562
+ res.json(user);
563
+ }
564
+ };
565
+ ```
566
+
474
567
  ### Route Config
475
568
 
476
569
  Add a `.js` file alongside your `.njk` file to configure the route:
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Schema Applicator
3
+ * Parses and validates request input against compiled schemas
4
+ */
5
+
6
+ /**
7
+ * Apply compiled schema to request
8
+ * Parses body, params, and query against their respective schemas
9
+ * Stores validated results in req.input
10
+ *
11
+ * @param {Object} req - Express request object
12
+ * @param {Object|null} compiledSchema - Compiled schema from compileSchema
13
+ * @returns {void}
14
+ * @throws {ZodError} If validation fails
15
+ */
16
+ function applySchema(req, compiledSchema) {
17
+ // Initialize req.input
18
+ req.input = {
19
+ body: undefined,
20
+ params: undefined,
21
+ query: undefined
22
+ };
23
+
24
+ // No schema means no validation
25
+ if (!compiledSchema) {
26
+ return;
27
+ }
28
+
29
+ // Parse body if schema exists
30
+ if (compiledSchema.body) {
31
+ req.input.body = compiledSchema.body.parse(req.body);
32
+ }
33
+
34
+ // Parse params if schema exists
35
+ if (compiledSchema.params) {
36
+ req.input.params = compiledSchema.params.parse(req.params);
37
+ }
38
+
39
+ // Parse query if schema exists
40
+ if (compiledSchema.query) {
41
+ req.input.query = compiledSchema.query.parse(req.query);
42
+ }
43
+ }
44
+
45
+ module.exports = {
46
+ applySchema
47
+ };
48
+
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Schema Compiler
3
+ * Compiles schema definitions from API files
4
+ */
5
+
6
+ const z = require('zod');
7
+ const schemaCache = require('../utils/schemaCache');
8
+
9
+ /**
10
+ * Compile schema from an API module
11
+ * @param {string} filePath - Absolute file path to API module
12
+ * @param {Object} apiModule - The loaded API module
13
+ * @returns {Object|null} Compiled schema object or null if no schema
14
+ */
15
+ function compileSchema(filePath, apiModule) {
16
+ // Return cached schema if exists
17
+ if (schemaCache.has(filePath)) {
18
+ return schemaCache.get(filePath);
19
+ }
20
+
21
+ // Check if module exports schema
22
+ const schemaFn = apiModule.schema;
23
+
24
+ // Schema is optional
25
+ if (schemaFn === undefined) {
26
+ schemaCache.set(filePath, null);
27
+ return null;
28
+ }
29
+
30
+ // Schema must be a function
31
+ if (typeof schemaFn !== 'function') {
32
+ throw new Error(`Schema in ${filePath} must be a function`);
33
+ }
34
+
35
+ // Call schema function with { z }
36
+ const compiled = schemaFn({ z });
37
+
38
+ // Validate compiled schema structure
39
+ if (compiled !== null && typeof compiled !== 'object') {
40
+ throw new Error(`Schema function in ${filePath} must return an object or null`);
41
+ }
42
+
43
+ // Cache and return
44
+ schemaCache.set(filePath, compiled);
45
+ return compiled;
46
+ }
47
+
48
+ /**
49
+ * Clear schema cache for a file (for hot-reload)
50
+ * @param {string} filePath - Absolute file path
51
+ */
52
+ function invalidateSchema(filePath) {
53
+ schemaCache.del(filePath);
54
+ }
55
+
56
+ /**
57
+ * Clear all cached schemas
58
+ */
59
+ function clearAllSchemas() {
60
+ schemaCache.clear();
61
+ }
62
+
63
+ module.exports = {
64
+ compileSchema,
65
+ invalidateSchema,
66
+ clearAllSchemas
67
+ };
68
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webspresso",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "Minimal, production-ready SSR framework for Node.js with file-based routing, Nunjucks templating, built-in i18n, and CLI tooling",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -29,14 +29,17 @@
29
29
  "files": [
30
30
  "index.js",
31
31
  "bin/",
32
- "src/"
32
+ "src/",
33
+ "utils/",
34
+ "core/"
33
35
  ],
34
36
  "dependencies": {
35
37
  "commander": "^11.1.0",
36
38
  "express": "^4.18.2",
37
39
  "helmet": "^7.2.0",
38
40
  "inquirer": "^8.2.6",
39
- "nunjucks": "^3.2.4"
41
+ "nunjucks": "^3.2.4",
42
+ "zod": "^3.23.0"
40
43
  },
41
44
  "peerDependencies": {
42
45
  "dotenv": "^16.0.0"
@@ -449,3 +449,4 @@ module.exports = {
449
449
  matchPattern
450
450
  };
451
451
 
452
+
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Schema Cache
3
+ * Caches compiled Zod schemas per file path
4
+ */
5
+
6
+ const cache = new Map();
7
+
8
+ /**
9
+ * Get cached schema for a file path
10
+ * @param {string} filePath - Absolute file path
11
+ * @returns {Object|undefined} Compiled schema or undefined
12
+ */
13
+ function get(filePath) {
14
+ return cache.get(filePath);
15
+ }
16
+
17
+ /**
18
+ * Set compiled schema for a file path
19
+ * @param {string} filePath - Absolute file path
20
+ * @param {Object} schema - Compiled schema object
21
+ */
22
+ function set(filePath, schema) {
23
+ cache.set(filePath, schema);
24
+ }
25
+
26
+ /**
27
+ * Check if schema exists in cache
28
+ * @param {string} filePath - Absolute file path
29
+ * @returns {boolean}
30
+ */
31
+ function has(filePath) {
32
+ return cache.has(filePath);
33
+ }
34
+
35
+ /**
36
+ * Clear all cached schemas
37
+ * Useful for development hot-reload
38
+ */
39
+ function clear() {
40
+ cache.clear();
41
+ }
42
+
43
+ /**
44
+ * Delete a specific schema from cache
45
+ * @param {string} filePath - Absolute file path
46
+ * @returns {boolean} True if deleted
47
+ */
48
+ function del(filePath) {
49
+ return cache.delete(filePath);
50
+ }
51
+
52
+ module.exports = {
53
+ get,
54
+ set,
55
+ has,
56
+ clear,
57
+ del
58
+ };
59
+