webspresso 0.0.95 → 0.0.96

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) 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/.cursorrules +14 -0
  23. package/AGENTS.md +175 -0
  24. package/CLAUDE.md +54 -0
  25. package/README.md +18 -14
  26. package/bin/commands/agents-init.js +169 -0
  27. package/bin/commands/api.js +114 -48
  28. package/bin/commands/doctor.js +35 -0
  29. package/bin/commands/new.js +13 -2
  30. package/bin/commands/page.js +75 -89
  31. package/bin/commands/service.js +77 -0
  32. package/bin/webspresso.js +2 -2
  33. package/core/compileSchema.js +1 -1
  34. package/core/errors/middleware.js +22 -2
  35. package/core/errors/normalize.js +29 -2
  36. package/{utils/schemaCache.js → core/schema-cache.js} +2 -3
  37. package/index.d.ts +306 -0
  38. package/index.js +35 -1
  39. package/package.json +5 -3
  40. package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +22 -1
  41. package/plugins/csrf/index.js +3 -2
  42. package/plugins/csv/engine.js +447 -0
  43. package/plugins/csv/index.js +94 -0
  44. package/plugins/csv/middleware.js +80 -0
  45. package/plugins/csv/services.js +138 -0
  46. package/plugins/file-manager/admin-component.js +613 -0
  47. package/plugins/file-manager/api-handlers.js +261 -0
  48. package/plugins/file-manager/core.js +601 -0
  49. package/plugins/file-manager/index.js +139 -0
  50. package/plugins/file-manager/security.js +207 -0
  51. package/plugins/index.js +8 -0
  52. package/plugins/xlsx/engine.js +397 -0
  53. package/plugins/xlsx/index.js +91 -0
  54. package/plugins/xlsx/middleware.js +87 -0
  55. package/plugins/xlsx/services.js +118 -0
  56. package/src/api/api-loader.js +233 -0
  57. package/src/api/define-api.js +35 -0
  58. package/src/discovery/file-route-parser.js +201 -0
  59. package/src/discovery/scan-routes.js +265 -0
  60. package/src/file-router.js +6 -0
  61. package/src/modules/define-module.js +99 -0
  62. package/src/modules/module-discovery.js +117 -0
  63. package/src/pages/define-page.js +36 -0
  64. package/src/pages/page-loader.js +214 -0
  65. package/src/routing/mount-discovered-routes.js +78 -0
  66. package/src/routing/route-table.js +191 -0
  67. package/src/server.js +112 -15
  68. package/src/services/builtins/file-manager.js +178 -0
  69. package/src/services/index.js +2 -0
  70. package/bin/commands/skill.js +0 -187
  71. package/templates/skills/webspresso-usage/REFERENCE-framework.md +0 -289
  72. package/templates/skills/webspresso-usage/REFERENCE-kernel.md +0 -93
  73. package/templates/skills/webspresso-usage/SKILL.md +0 -48
@@ -0,0 +1,341 @@
1
+ # Webspresso Services Layer (`services/`, `src/services/`)
2
+
3
+ The **Services Layer** is a lightweight, framework-agnostic architectural abstraction designed to encapsulate business logic, multi-step domain mutations, and data queries into isolated, reusable, testable functions.
4
+
5
+ ---
6
+
7
+ ## ⚠️ STRICT RULE: Encapsulate in `services/` (NO `controllers/`)
8
+
9
+ ### ❌ BAD vs ✅ GOOD Code Comparison
10
+
11
+ ```javascript
12
+ // ❌ WRONG (Controller pattern / manual transactions — NEVER DO THIS)
13
+ // File: controllers/UserController.js
14
+ class UserController {
15
+ async register(req, res) {
16
+ const trx = await knex.transaction(); // manual trx prone to leak
17
+ try {
18
+ const user = await trx('users').insert(...);
19
+ await trx.commit();
20
+ res.json(user);
21
+ } catch (e) {
22
+ await trx.rollback();
23
+ res.status(500).json({ error: e.message });
24
+ }
25
+ }
26
+ }
27
+
28
+ // ✅ CORRECT (Webspresso Service with declarative validation & automatic transaction)
29
+ // File: services/user/register.js
30
+ 'use strict';
31
+ const { z } = require('zod');
32
+
33
+ module.exports = {
34
+ schema: z.object({
35
+ email: z.string().email(),
36
+ password: z.string().min(8),
37
+ }),
38
+ transaction: true, // Automatically wraps in ACID transaction
39
+ handler: async (input, { db, service }) => {
40
+ const user = await db.getRepository('User').create(input);
41
+ return { success: true, user };
42
+ },
43
+ };
44
+ ```
45
+
46
+ - **NEVER** create a `controllers/` or `src/controllers/` directory, and never write class-based controller singletons.
47
+ - **ALWAYS** place business logic, domain mutations, and reusable workflows in `services/domain/action.js`.
48
+ - Services automatically provide Zod input validation (`schema`), role-based authorization (`auth`), automatic ACID transaction propagation (`transaction: true`), and caching (`cache`).
49
+ - Invoke services from API routes (`req.service('name', input)`), SSR loaders (`ctx.service('name', input)`), or other services (`ctx.service(...)`).
50
+
51
+ ---
52
+
53
+ ## 1. Directory Structure & Auto-Discovery
54
+
55
+ Services are located in the `services/` directory at the project root. Files automatically map to dot-separated service names and camelCase aliases.
56
+
57
+ ```
58
+ services/
59
+ ├── user/
60
+ │ ├── get.js → 'user.get'
61
+ │ ├── create.js → 'user.create'
62
+ │ └── reset-password.js → 'user.reset-password' & 'user.resetPassword'
63
+ ├── order-items/
64
+ │ └── get-by-id.js → 'order-items.get-by-id' & 'orderItems.getById'
65
+ └── billing/
66
+ └── invoice/
67
+ └── generate.js → 'billing.invoice.generate' & 'billing.invoice.generate'
68
+ ```
69
+
70
+ ### Discovery Mechanics
71
+ - **Cross-Platform Path Normalization**: Works seamlessly on Windows (`\`) and Unix (`/`).
72
+ - **Kebab & Snake to camelCase Aliases**: Files named with dashes or underscores automatically register both their raw dot-name and camelCase alias (e.g. `order-items/calculate_tax.js` is callable as both `'order-items.calculate_tax'` and `'orderItems.calculateTax'`).
73
+ - **File Extensions**: Discovers `.js` and `.cjs` files. Ignores files starting with `_` or `.` (e.g., `_helpers.js`).
74
+
75
+ ---
76
+
77
+ ## 2. Service Definition Contract
78
+
79
+ A service file exports a `handler` function and optional configuration properties:
80
+
81
+ ```javascript
82
+ // services/user/create.js
83
+ const { z } = require('zod');
84
+
85
+ module.exports = {
86
+ // Optional input validation schema (Zod)
87
+ schema: z.object({
88
+ email: z.string().email(),
89
+ name: z.string().min(2),
90
+ role: z.enum(['user', 'admin']).default('user'),
91
+ }),
92
+
93
+ // Optional authentication & role requirements
94
+ auth: 'admin', // or true, ['admin', 'manager'], or (user, ctx) => boolean
95
+
96
+ // Optional timeout in milliseconds (throws ServiceTimeoutError if exceeded)
97
+ timeout: 5000,
98
+
99
+ // Optional automatic ACID transaction wrapper
100
+ transaction: true,
101
+
102
+ // Optional response memoization / caching
103
+ cache: {
104
+ ttl: 60000, // TTL in milliseconds
105
+ key: (input) => `user:create:${input.email}`, // Custom key generator (defaults to SHA-256 of input)
106
+ },
107
+
108
+ // Service handler
109
+ async handler(input, ctx) {
110
+ const { email, name, role } = input;
111
+ const { db, trx } = ctx;
112
+
113
+ const user = await db.getRepository('User').create(
114
+ { email, name, role },
115
+ { trx } // Reuses the automatic service transaction
116
+ );
117
+
118
+ return user;
119
+ },
120
+ };
121
+ ```
122
+
123
+ ---
124
+
125
+ ## 3. Invocation & Composition (`ctx.service()`)
126
+
127
+ ### 3.1 From SSR Page Loaders
128
+ ```javascript
129
+ // pages/users/[id].js
130
+ module.exports = {
131
+ async load({ req, res, ctx }) {
132
+ const user = await ctx.service('user.get', { id: req.params.id });
133
+ return { user };
134
+ },
135
+ };
136
+ ```
137
+
138
+ ### 3.2 From Express API Handlers
139
+ ```javascript
140
+ // pages/api/users.js
141
+ module.exports = async function (req, res) {
142
+ const newUser = await req.service('user.create', req.body);
143
+ res.status(201).json({ data: newUser });
144
+ };
145
+ ```
146
+
147
+ ### 3.3 Nested Service Composition
148
+ Services can invoke other services via `ctx.service()`. The child service automatically inherits `ctx.db`, `ctx.auth`, `ctx.user`, `ctx.session`, and active transaction (`ctx.trx`):
149
+
150
+ ```javascript
151
+ // services/order/checkout.js
152
+ module.exports = {
153
+ transaction: true,
154
+ async handler({ orderId, items }, ctx) {
155
+ // 1. Process payment
156
+ await ctx.service('payment.charge', { orderId });
157
+
158
+ // 2. Deduct inventory (runs inside the exact same database transaction!)
159
+ await ctx.service('inventory.deduct', { items });
160
+
161
+ // 3. Send confirmation email (async)
162
+ await ctx.service('email.sendOrderConfirmation', { orderId });
163
+
164
+ return { status: 'completed' };
165
+ },
166
+ };
167
+ ```
168
+
169
+ ---
170
+
171
+ ## 4. Advanced Execution Features
172
+
173
+ ### 4.1 Authentication & RBAC (`auth`)
174
+ - `auth: true` — Requires `ctx.user` or `ctx.auth.user` to be present. Throws `UnauthorizedError` (HTTP 401) if unauthenticated.
175
+ - `auth: 'admin'` — Checks `user.role === 'admin'` or `user.roles.includes('admin')`. Throws `ForbiddenError` (HTTP 403) if unauthorized.
176
+ - `auth: ['admin', 'manager']` — Allows any of the specified roles.
177
+ - `auth: (user, ctx) => boolean` — Custom predicate function for dynamic authorization rules.
178
+ - `skipAuth: true` — Option passed in `ctx.service('service.name', input, { skipAuth: true })` to allow internal background crons or system tasks to bypass auth checks.
179
+
180
+ ### 4.2 ACID Transactions (`transaction: true`) & Ambient Propagation
181
+ - Wraps the service execution inside a Knex transaction `db.knex.transaction()` and registers it with `AsyncLocalStorage` ambient transaction storage.
182
+ - Automatically binds all ORM repository calls (`db.getRepository(...)`, `ctx.db.getRepository(...)`, query builders) executed in the async tree to the active transaction without manual `trx` argument passing.
183
+ - Exposes `ctx.trx` to the handler and repositories for explicit operations.
184
+ - Automatically commits when handler returns.
185
+ - Automatically rolls back on thrown errors or rejection.
186
+ - Child service calls (`ctx.service(...)`) automatically reuse the parent ambient `trx` without spawning duplicate root transactions or suffering deadlocks.
187
+
188
+ ### 4.3 Execution Timeout (`timeout: ms`)
189
+ - Protects against hanging database queries, unresponsive 3rd-party APIs, or deadlocks.
190
+ - Cleans up timers upon completion.
191
+ - Rejects with `ServiceTimeoutError` with descriptive duration message.
192
+
193
+ ### 4.4 In-Memory Memoization & Cache (`cache: { ttl, key }`)
194
+ - Prevents redundant computation or queries within TTL window.
195
+ - Generates deterministic SHA-256 cache keys from sorted JSON input representations.
196
+ - Supports custom `key(input)` serializer functions.
197
+ - Provides `services.clearCache(serviceName)` and `services.clearAllCaches()`.
198
+
199
+ ---
200
+
201
+ ## 5. Programmatic & Standalone Usage (CLI / Cron / Tests)
202
+
203
+ ```javascript
204
+ const { createServiceRegistry } = require('webspresso/services');
205
+
206
+ // Initialize registry directly
207
+ const services = createServiceRegistry({
208
+ servicesDir: path.join(__dirname, 'services'),
209
+ db,
210
+ context: { config: appConfig },
211
+ });
212
+
213
+ // Discover all services
214
+ await services.discover();
215
+
216
+ // Execute service directly
217
+ const result = await services.call('order.getById', { id: 123 });
218
+ ```
219
+
220
+ ---
221
+
222
+ ## 6. Built-in `auth.*` Services
223
+
224
+ When a database connection (`options.db`) is configured, Webspresso automatically registers production-ready, standardized authentication services into `serviceRegistry` (unless custom implementations are provided in `services/auth/`):
225
+
226
+ | Service Name | Description | Schema / Input | Guard / ACID |
227
+ |---|---|---|---|
228
+ | `auth.login` | Authenticates user credentials, sets session cookie and returns user data | `{ email, password, remember? }` | Validates hash, sets `req.session.user` |
229
+ | `auth.register` | Validates and hashes password, prevents duplicate emails, creates user record | `{ email, password, name?, role? }` | `transaction: true` |
230
+ | `auth.request-password-reset` | Generates secure password reset token (user enumeration resistant) | `{ email, ttlMs? }` | Returns `{ sent: true }` safely |
231
+ | `auth.reset-password` | Consumes token, validates TTL, updates password hash | `{ token, password }` | `transaction: true` |
232
+ | `auth.change-password` | Verifies current password and updates with new password | `{ currentPassword, newPassword }` | `auth: true`, `transaction: true` |
233
+ | `auth.verify-email` | Consumes verification token and updates `email_verified_at` | `{ token }` | `transaction: true` |
234
+
235
+ ### Customizing Built-in Auth Services
236
+ ```javascript
237
+ const { createAuthServices } = require('webspresso/services');
238
+
239
+ // Customize model name or column mappings
240
+ const customAuth = createAuthServices({
241
+ userModel: 'Account',
242
+ fields: {
243
+ email: 'email_address',
244
+ password: 'password_hash',
245
+ emailVerifiedAt: 'verified_at',
246
+ },
247
+ });
248
+ ```
249
+
250
+ ---
251
+
252
+ ## 7. Built-in `mail.*` Services
253
+
254
+ When `emailPlugin` is loaded, Webspresso automatically registers standardized transactional email services into `serviceRegistry`:
255
+
256
+ | Service Name | Description | Schema / Input | Features |
257
+ |---|---|---|---|
258
+ | `mail.send` | Sends HTML/plain/MJML transactional email via Nodemailer | `{ to, subject, html?, text?, mjml?, template?, data?, attachments? }` | DB send logging, MJML compilation |
259
+ | `mail.send-templated` | Sends pre-registered MJML template with payload variables | `{ template, to, subject, data?, from?, replyTo? }` | Template caching & variable interpolation |
260
+ | `mail.preview` | Compiles and previews MJML/template without sending | `{ template?, mjml?, data? }` | In-memory HTML & text preview rendering |
261
+ | `mail.query-logs` | Queries email send logs and delivery status | `{ limit?, offset?, status?, to?, template? }` | `auth: 'admin'` |
262
+
263
+ ### Customizing or Standalone Mail Services
264
+ ```javascript
265
+ const { createMailServices } = require('webspresso/services');
266
+
267
+ // Initialize standalone mail services
268
+ const mail = createMailServices({
269
+ emailService,
270
+ registry: templateRegistry,
271
+ db: appDb,
272
+ tableName: 'email_logs',
273
+ });
274
+ ```
275
+
276
+ ---
277
+
278
+ ## 8. Built-in `media.*` Services
279
+
280
+ When `uploadPlugin` is loaded, Webspresso automatically registers standardized file upload and media handling services into `serviceRegistry`:
281
+
282
+ | Service Name | Description | Schema / Input | Features |
283
+ |---|---|---|---|
284
+ | `media.upload` | Saves Buffer, Base64 data URL, or local file to storage | `{ buffer?, base64?, filePath?, originalName?, mimeType?, destDir?, publicBasePath? }` | Safe filename generation, MIME extraction, storage provider abstraction |
285
+ | `media.delete` | Safely removes stored file from disk/provider | `{ key, destDir? }` | Strict path traversal protection (`..` prevention) |
286
+ | `media.info` | Retrieves file existence, size, and modified timestamp | `{ key, destDir? }` | Fast filesystem stat inspection |
287
+
288
+ ### Customizing or Standalone Media Services
289
+ ```javascript
290
+ const { createMediaServices } = require('webspresso/services');
291
+
292
+ // Initialize standalone media services
293
+ const media = createMediaServices({
294
+ destDir: '/path/to/custom/uploads',
295
+ publicBasePath: '/static/uploads',
296
+ });
297
+ ```
298
+
299
+ ---
300
+
301
+ ## 9. Built-in `system.*` Services
302
+
303
+ Webspresso automatically registers core system diagnostics, maintenance, and cache invalidation services into `serviceRegistry`:
304
+
305
+ | Service Name | Description | Schema / Input | Guard / Features |
306
+ |---|---|---|---|
307
+ | `system.health` | Live DB ping, latency measurement, memory footprint (`rss`, `heap`), uptime | `{ detailed? }` | Zero downtime monitoring & load balancer health checks |
308
+ | `system.cleanup` | Purges expired auth tokens and old audit logs | `{ olderThanDays?, purgeAuthTokens?, purgeAuditLogs? }` | `auth: 'admin'`, cron/worker ready |
309
+ | `system.cache-flush` | Flushes ORM query cache and service memoization | `{ scope?: 'all'\|'orm'\|'services', model? }` | `auth: 'admin'` |
310
+ | `system.info` | Runtime diagnostics: Node version, platform, registered models, plugins & services | `{}` | `auth: 'admin'` |
311
+
312
+ ### Customizing or Standalone System Services
313
+ ```javascript
314
+ const { createSystemServices } = require('webspresso/services');
315
+
316
+ // Initialize standalone system services
317
+ const system = createSystemServices({
318
+ db,
319
+ serviceRegistry,
320
+ pluginManager,
321
+ });
322
+ ```
323
+
324
+ ---
325
+
326
+ ## 10. Built-in `exchange.*` Services
327
+
328
+ When `dataExchangePlugin` is loaded, Webspresso automatically registers spreadsheet export and import services into `serviceRegistry`:
329
+
330
+ | Service Name | Description | Schema / Input | Guard / Features |
331
+ |---|---|---|---|
332
+ | `exchange.export` | Exports ORM model records to Excel (.xlsx) buffer | `{ model, ids?, where?, limit? }` | `auth: 'admin'`, frozen header rows, clean output sanitization |
333
+ | `exchange.import` | Imports CSV or XLSX spreadsheets into database models | `{ model, buffer?, base64?, csvText?, mode?: 'insert'\|'upsert', upsertKey? }` | `auth: 'admin'`, `transaction: true` (atomic batch rollback), column type coercion |
334
+
335
+ ### Customizing or Standalone Exchange Services
336
+ ```javascript
337
+ const { createExchangeServices } = require('webspresso/services');
338
+
339
+ // Initialize standalone exchange services
340
+ const exchange = createExchangeServices({ db });
341
+ ```
@@ -0,0 +1,67 @@
1
+ # Webspresso Graceful Shutdown & Lifecycle Management Guide
2
+
3
+ [← Back to AGENTS.md](.agents/AGENTS.md)
4
+
5
+ ---
6
+
7
+ ## 1. Overview & Architecture
8
+
9
+ Webspresso includes a framework-level lifecycle coordinator (`ShutdownManager`) and HTTP adapter (`NodeHttpAdapter`) designed to cleanly drain active requests, close keep-alive sockets, and execute reverse-order disposer functions during server termination (`SIGINT`, `SIGTERM`, or `app.close()`).
10
+
11
+ ---
12
+
13
+ ## 2. Configuration & Modes
14
+
15
+ Configured via `createApp({ server: { shutdown } })` or `shutdown`:
16
+
17
+ ```js
18
+ const { app } = createApp({
19
+ pagesDir: './pages',
20
+ server: {
21
+ shutdown: {
22
+ enabled: true, // Default: true
23
+ mode: 'graceful', // 'graceful' | 'force'
24
+ timeout: 10_000, // Max time in ms to wait for active requests (Default: 10000)
25
+ signals: ['SIGINT', 'SIGTERM'],
26
+ },
27
+ },
28
+ });
29
+ ```
30
+
31
+ ### Shutdown Modes:
32
+ - **`graceful` (Default)**: Rejects new connections, drains active in-flight requests, and closes idle/keep-alive connections.
33
+ - **`force`**: Immediately destroys all active and idle sockets (`forceClose`). Recommended for rapid development/watch server reloads to avoid port lock.
34
+
35
+ ---
36
+
37
+ ## 3. Application Lifecycle Hooks
38
+
39
+ ```js
40
+ // Register cleanup hook
41
+ app.onShutdown(async () => {
42
+ await db.destroy();
43
+ await redis.quit();
44
+ });
45
+
46
+ // Manual close (does not call process.exit())
47
+ await app.close('Maintenance');
48
+ ```
49
+
50
+ ---
51
+
52
+ ## 4. Plugin Disposer Lifecycle
53
+
54
+ Plugins can return a disposer function from `register(ctx)` or implement `shutdown()` / `dispose()` methods:
55
+
56
+ ```js
57
+ const customPlugin = {
58
+ name: 'my-worker',
59
+ register(ctx) {
60
+ const worker = startWorker();
61
+ return async () => {
62
+ await worker.stop();
63
+ };
64
+ },
65
+ };
66
+ ```
67
+ During shutdown, plugin disposers are executed sequentially in **reverse dependency order**.
@@ -0,0 +1,73 @@
1
+ # Webspresso SSR Streaming & Chunked Transfer Guide
2
+
3
+ Webspresso supports zero-dependency, native HTTP chunked response streaming (`Transfer-Encoding: chunked`) for Server-Side Rendered (SSR) Nunjucks templates.
4
+
5
+ ---
6
+
7
+ ## 1. Why SSR Streaming?
8
+
9
+ In traditional SSR, the server must wait for all database queries and API calls to complete before compiling the entire HTML string and sending it with `res.send(html)`.
10
+
11
+ With **SSR Streaming** (`res.renderStream`):
12
+ 1. **Instant TTFB**: HTML `<head>`, stylesheets, layout, and loading skeletons are flushed to the client immediately (`res.flushHeaders()`).
13
+ 2. **Early Resource Preloading**: Browsers begin downloading CSS, Google Fonts, and JavaScript while backend databases are still executing queries.
14
+ 3. **Out-of-Order Slot Resolution**: As asynchronous promises resolve, HTML replacement chunks stream into open slots seamlessly.
15
+
16
+ ---
17
+
18
+ ## 2. Basic Route Streaming (`res.renderStream`)
19
+
20
+ ```js
21
+ // In any Express route handler:
22
+ app.get('/dashboard', async (req, res) => {
23
+ const user = await fetchUser(req);
24
+
25
+ const deferredData = {
26
+ analytics: fetchHeavyAnalytics(), // Promise<any>
27
+ recentOrders: fetchOrders(), // Promise<any>
28
+ };
29
+
30
+ await res.renderStream('dashboard.njk', { user }, {
31
+ defer: deferredData,
32
+ });
33
+ });
34
+ ```
35
+
36
+ ### In Nunjucks Template (`views/dashboard.njk`):
37
+ ```html
38
+ {% extends "layout.njk" %}
39
+
40
+ {% block content %}
41
+ <h1>Welcome, {{ user.name }}</h1>
42
+
43
+ <!-- Slot target with fallback skeleton -->
44
+ <div data-stream-target="analytics">
45
+ <div class="skeleton-loader">Loading analytics...</div>
46
+ </div>
47
+
48
+ <div data-stream-target="recentOrders">
49
+ <div class="skeleton-loader">Loading orders...</div>
50
+ </div>
51
+ {% endblock %}
52
+ ```
53
+
54
+ ---
55
+
56
+ ## 3. Page File Router Streaming (`pages/`)
57
+
58
+ In file-based page loaders (`pages/products/index.js` or `pages/dashboard.js`), simply return `defer` or `{ stream: true }`:
59
+
60
+ ```js
61
+ // pages/products/index.js
62
+ export async function load({ req, db }) {
63
+ const categories = await db.getRepository('Category').find();
64
+
65
+ return {
66
+ stream: true,
67
+ categories,
68
+ defer: {
69
+ products: db.getRepository('Product').query().where('active', true).paginate({ limit: 50 }),
70
+ },
71
+ };
72
+ }
73
+ ```
@@ -0,0 +1,27 @@
1
+ # Webspresso Development & Testing Workflow
2
+
3
+ [← Back to AGENTS.md](.agents/AGENTS.md)
4
+
5
+ ---
6
+
7
+ ## 1. Essential Commands
8
+
9
+ - **Dev Server**: `npm run dev` or `webspresso dev`
10
+ - **Run Unit Tests**: `npm test` or `npx vitest run`
11
+ - **Run Targeted Test File**: `npx vitest run tests/unit/auth/jwt.test.js`
12
+ - **Check TypeScript Types**: `npm run check:types` (`tsc --project tests/ts-smoke/tsconfig.json`)
13
+ - **Run E2E Tests**: `npm run test:e2e` (Playwright)
14
+ - **Run Migrations**: `npx webspresso db:migrate`
15
+ - **Project Sanity Check**: `npx webspresso doctor`
16
+
17
+ ---
18
+
19
+ ## 2. Testing & Quality Assurance Rules
20
+
21
+ ### 2.1 Zero Regression Policy
22
+ - Always run Vitest tests (`npm test` or `npx vitest run`) before declaring any task complete.
23
+ - Verify clean 100% test passes across unit, integration, and E2E test suites.
24
+
25
+ ### 2.2 API Backward Compatibility
26
+ - Never break existing public APIs (`registerModule`, `registerPage`, `adminApi`, `createApp`, `defineModel`).
27
+ - Always add new options or parameters as optional or backward-compatible enhancements.