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.
Files changed (184) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -3
  3. package/bin/commands/add-esbuild.js +7 -3
  4. package/bin/commands/add-tailwind.js +9 -5
  5. package/bin/commands/build.js +20 -4
  6. package/bin/commands/dev.js +57 -7
  7. package/bin/commands/doctor.js +42 -1
  8. package/bin/commands/favicon-generate.js +10 -3
  9. package/bin/commands/new.js +8 -3
  10. package/bin/commands/start.js +3 -1
  11. package/bin/utils/add-command.js +16 -0
  12. package/core/app.js +45 -3
  13. package/core/auth/authenticate.js +3 -3
  14. package/core/auth/hash.js +167 -43
  15. package/core/auth/index.js +13 -1
  16. package/core/auth/jwt.js +21 -2
  17. package/core/auth/manager.js +63 -14
  18. package/core/auth/middleware.js +4 -2
  19. package/core/auth/oauth.js +5 -1
  20. package/core/auth/tokens.js +7 -2
  21. package/core/auth/totp.js +42 -6
  22. package/core/auth/user-agent.js +2 -0
  23. package/core/client/adapter/datastar.js +1401 -0
  24. package/core/client/api.js +1337 -0
  25. package/core/client/controller.js +1706 -0
  26. package/core/client/debug.js +367 -0
  27. package/core/client/devtool.js +1134 -0
  28. package/core/client/forms.js +1164 -0
  29. package/core/client/fragments.js +894 -0
  30. package/core/client/index.d.ts +59 -0
  31. package/core/client/index.js +391 -0
  32. package/core/client/lite.js +175 -0
  33. package/core/client/server-fragments.js +868 -0
  34. package/core/client/storage.js +339 -0
  35. package/core/client/utils.js +281 -0
  36. package/core/compression/middleware.js +277 -26
  37. package/core/context/app-context.js +25 -3
  38. package/core/context/request.js +32 -4
  39. package/core/errors/middleware.js +14 -0
  40. package/core/events/dispatcher.js +33 -0
  41. package/core/forms/flash.js +136 -102
  42. package/core/health/middleware.js +36 -6
  43. package/core/http/pipeline.js +45 -6
  44. package/core/index.js +25 -7
  45. package/core/kernel/view.js +41 -10
  46. package/core/metrics/middleware.js +30 -6
  47. package/core/metrics/registry.js +13 -5
  48. package/core/orm/cache/fingerprint.js +32 -11
  49. package/core/orm/cache/layer.js +19 -2
  50. package/core/orm/hidden.js +145 -0
  51. package/core/orm/model.js +48 -2
  52. package/core/orm/query-builder.js +343 -6
  53. package/core/orm/query-filters.js +14 -2
  54. package/core/orm/repository.js +64 -13
  55. package/core/orm/schema-helpers.js +9 -6
  56. package/core/orm/transaction.js +50 -6
  57. package/core/orm/types.js +4 -0
  58. package/core/queue/adapters/database.js +184 -2
  59. package/core/queue/adapters/memory.js +153 -5
  60. package/core/queue/adapters/redis.js +59 -4
  61. package/core/queue/job.js +14 -1
  62. package/core/queue/manager.js +115 -0
  63. package/core/resilience/circuit-breaker.js +27 -8
  64. package/core/services/builtins/exchange.js +174 -7
  65. package/core/services/builtins/file-manager.js +36 -0
  66. package/core/services/builtins/mail.js +27 -0
  67. package/core/services/builtins/queue.js +144 -0
  68. package/core/services/builtins/security.js +118 -0
  69. package/core/services/builtins/webhook.js +110 -0
  70. package/core/services/executor.js +38 -16
  71. package/core/services/index.js +4 -1
  72. package/core/sse/stream.js +28 -5
  73. package/core/utils/timeout.js +8 -1
  74. package/core/validation/compiler.js +10 -7
  75. package/index.d.ts +1320 -74
  76. package/index.js +128 -8
  77. package/package.json +75 -27
  78. package/plugins/admin-panel/client/parts/08-record-list.js +717 -70
  79. package/plugins/admin-panel/index.js +19 -8
  80. package/plugins/csrf/index.js +3 -3
  81. package/plugins/data-exchange/export.js +214 -0
  82. package/plugins/data-exchange/import.js +48 -5
  83. package/plugins/data-exchange/index.js +70 -28
  84. package/plugins/data-exchange/preview.js +266 -0
  85. package/plugins/data-exchange/template.js +242 -0
  86. package/plugins/email/admin-component.js +783 -141
  87. package/plugins/email/api-handlers.js +51 -1
  88. package/plugins/email/index.js +5 -0
  89. package/plugins/email/service.js +48 -0
  90. package/plugins/file-manager/admin-component.js +1546 -70
  91. package/plugins/file-manager/api-handlers.js +166 -1
  92. package/plugins/file-manager/core.js +598 -8
  93. package/plugins/file-manager/index.js +8 -0
  94. package/plugins/index.js +7 -0
  95. package/plugins/mcp/auth.js +3 -3
  96. package/plugins/oauth/index.js +36 -3
  97. package/plugins/queue/admin-component.js +1092 -0
  98. package/plugins/queue/api-handlers.js +248 -0
  99. package/plugins/queue/index.js +69 -0
  100. package/plugins/rest-resources/index.js +7 -2
  101. package/plugins/security/admin-component.js +689 -0
  102. package/plugins/security/api-handlers.js +169 -0
  103. package/plugins/security/audit.js +153 -0
  104. package/plugins/security/bruteforce.js +244 -0
  105. package/plugins/security/firewall.js +459 -0
  106. package/plugins/security/headers.js +214 -0
  107. package/plugins/security/honeypot.js +234 -0
  108. package/plugins/security/incident-recorder.js +137 -0
  109. package/plugins/security/index.js +211 -0
  110. package/plugins/seo-checker/analyzer.js +20 -7
  111. package/plugins/upload/index.js +36 -0
  112. package/plugins/webhooks/admin-component.js +702 -175
  113. package/plugins/webhooks/api-handlers.js +75 -1
  114. package/plugins/webhooks/dispatcher.js +290 -11
  115. package/plugins/webhooks/index.js +37 -2
  116. package/plugins/webhooks/sender.js +56 -0
  117. package/plugins/xlsx/engine.js +19 -4
  118. package/src/api/api-loader.js +19 -1
  119. package/src/bundle/index.js +443 -17
  120. package/src/client-runtime/bootstrap-swup.js +9 -0
  121. package/src/client-runtime/mount.js +15 -20
  122. package/src/client-runtime/resolve.js +3 -2
  123. package/src/dev/api-typegen.js +1300 -0
  124. package/src/dev/banner.js +10 -1
  125. package/src/dev/colors.js +1 -1
  126. package/src/dev/index.js +7 -1
  127. package/src/dev/typegen.js +173 -17
  128. package/src/dev-reload/index.js +1 -1
  129. package/src/discovery/file-route-parser.js +17 -3
  130. package/src/discovery/scan-routes.js +4 -1
  131. package/src/file-router.js +253 -69
  132. package/src/helpers.js +46 -26
  133. package/src/njk-frontmatter.js +30 -4
  134. package/src/routing/cascading.js +26 -8
  135. package/src/routing/mount-discovered-routes.js +76 -0
  136. package/src/server.js +231 -70
  137. package/views/partials/webspresso-client-runtime.njk +1 -9
  138. package/.agents/ADMIN.md +0 -426
  139. package/.agents/AGENTS.md +0 -96
  140. package/.agents/ARCHITECTURE.md +0 -163
  141. package/.agents/AUTH.md +0 -581
  142. package/.agents/CACHE.md +0 -148
  143. package/.agents/CLI.md +0 -649
  144. package/.agents/COMPRESSION.md +0 -59
  145. package/.agents/CONTEXT.md +0 -152
  146. package/.agents/CONVENTIONS.md +0 -50
  147. package/.agents/EARLY_HINTS.md +0 -120
  148. package/.agents/EMAIL.md +0 -223
  149. package/.agents/ERRORS.md +0 -260
  150. package/.agents/EVENTS.md +0 -179
  151. package/.agents/FLAGS.md +0 -118
  152. package/.agents/HEALTH.md +0 -118
  153. package/.agents/LIMITS.md +0 -109
  154. package/.agents/LOGGING.md +0 -93
  155. package/.agents/MCP.md +0 -103
  156. package/.agents/METRICS.md +0 -127
  157. package/.agents/MIGRATION_JOBS.md +0 -95
  158. package/.agents/ORM.md +0 -700
  159. package/.agents/PLUGINS.md +0 -729
  160. package/.agents/QUEUE.md +0 -290
  161. package/.agents/RATE_LIMIT.md +0 -147
  162. package/.agents/REALTIME.md +0 -213
  163. package/.agents/RESILIENCE.md +0 -235
  164. package/.agents/ROUTING.md +0 -605
  165. package/.agents/SCALE_OUT.md +0 -109
  166. package/.agents/SECRETS.md +0 -117
  167. package/.agents/SERVICES.md +0 -341
  168. package/.agents/SHUTDOWN.md +0 -74
  169. package/.agents/STREAMING.md +0 -108
  170. package/.agents/WORKFLOW.md +0 -27
  171. package/.agents/internal/DOCUMENTATION_REVIEW.md +0 -485
  172. package/.agents/internal/TECHNICAL_REVIEW.md +0 -428
  173. package/.agents/skills/webspresso-admin-and-plugins/SKILL.md +0 -122
  174. package/.agents/skills/webspresso-framework/SKILL.md +0 -89
  175. package/.agents/skills/webspresso-orm-and-database/SKILL.md +0 -127
  176. package/.agents/skills/webspresso-routing-and-api/SKILL.md +0 -130
  177. package/.agents/skills/webspresso-services/SKILL.md +0 -134
  178. package/.cursorrules +0 -14
  179. package/AGENTS.md +0 -203
  180. package/CLAUDE.md +0 -54
  181. package/core/kernel/run-demo.js +0 -58
  182. package/plugins/admin-panel/styles.js +0 -9
  183. package/plugins/data-exchange/export-xlsx.js +0 -119
  184. package/src/client-runtime/bootstrap-alpine-swup.js +0 -34
package/.agents/ERRORS.md DELETED
@@ -1,260 +0,0 @@
1
- # Webspresso Framework Exceptions & Error Handling Guide
2
-
3
- [← Back to AGENTS.md](.agents/AGENTS.md)
4
-
5
- ---
6
-
7
- ## ⚠️ STRICT RULE: Throw Semantic Errors (NO Generic `new Error()` or Manual `res.status`)
8
-
9
- - **NEVER** return manual error payloads like `return res.status(404).json(...)` inside services or loaders.
10
- - **NEVER** throw a generic `throw new Error('Not found')` for business or client-facing issues. In production, unmapped `Error` instances are automatically masked as `500 Internal Server Error` with stack traces hidden!
11
- - **ALWAYS** throw semantic framework exceptions:
12
- - `throw new NotFoundError('Item not found')` → returns HTTP 404
13
- - `throw new ValidationError('Invalid data', { fields: { ... } })` → returns HTTP 422
14
- - `throw new UnauthorizedError('Please log in')` → returns HTTP 401
15
- - `throw new ForbiddenError('Access denied')` → returns HTTP 403
16
- - `throw new BadRequestError('Invalid query')` → returns HTTP 400
17
-
18
- ---
19
-
20
- ## 1. Overview & Exception Hierarchy
21
-
22
- Webspresso features a centralized, lightweight, zero-dependency exception hierarchy inspired by Django's failure domain separation. Thrown synchronous and asynchronous exceptions are automatically caught by the global error boundary, normalized, and formatted as standardized JSON responses for API routes or styled HTML pages for web clients.
23
-
24
- ```text
25
- WebspressoError (base class with cause, code, details)
26
- ├── ConfigurationError (bootstrap & config errors)
27
- ├── PluginError (plugin lifecycle & dependency errors)
28
- ├── ValidationError (validation failures with fields, 422 default)
29
- ├── SecurityError (suspicious requests, CSRF/cookie tampering, 400 default)
30
- ├── RequestError
31
- │ └── RequestAbortedError (client socket disconnection, 499 default)
32
- ├── RouterError
33
- │ ├── RouteNotFoundError (404 URL resolution error)
34
- │ └── RouteGenerationError (named route generation error)
35
- └── HttpError (base for HTTP-mapped exceptions)
36
- ├── BadRequestError (400)
37
- ├── UnauthorizedError (401)
38
- ├── ForbiddenError (403)
39
- ├── NotFoundError (404)
40
- ├── MethodNotAllowedError (405)
41
- ├── ConflictError (409)
42
- ├── PayloadTooLargeError (413)
43
- ├── UnsupportedMediaTypeError (415)
44
- ├── UnprocessableEntityError (422)
45
- └── TooManyRequestsError (429)
46
- ```
47
-
48
- ---
49
-
50
- ## 2. Public Imports & Subpath
51
-
52
- ```js
53
- import {
54
- WebspressoError,
55
- HttpError,
56
- BadRequestError,
57
- UnauthorizedError,
58
- ForbiddenError,
59
- NotFoundError,
60
- MethodNotAllowedError,
61
- ConflictError,
62
- PayloadTooLargeError,
63
- UnsupportedMediaTypeError,
64
- UnprocessableEntityError,
65
- TooManyRequestsError,
66
- ValidationError,
67
- ConfigurationError,
68
- PluginError,
69
- SecurityError,
70
- RequestAbortedError,
71
- RouteNotFoundError,
72
- RouteGenerationError,
73
- } from 'webspresso';
74
-
75
- // Or via subpath:
76
- import { NotFoundError, ValidationError } from 'webspresso/errors';
77
- ```
78
-
79
- ---
80
-
81
- ## 3. Usage Examples
82
-
83
- ### 3.1 HTTP Exceptions in API Handlers
84
- ```js
85
- // pages/api/users/[id].get.js
86
- import { NotFoundError } from 'webspresso';
87
-
88
- module.exports = async function handler(req, res) {
89
- const user = await req.db.getRepository('User').findById(req.params.id);
90
- if (!user) {
91
- throw new NotFoundError('User not found', {
92
- code: 'USER_NOT_FOUND',
93
- });
94
- }
95
- res.json({ user });
96
- };
97
- ```
98
-
99
- ### 3.2 Structured Validation Errors (422)
100
- ```js
101
- import { ValidationError } from 'webspresso';
102
-
103
- throw new ValidationError('Validation failed', {
104
- code: 'INVALID_INPUT',
105
- fields: {
106
- email: ['Invalid email address'],
107
- age: ['Must be at least 18'],
108
- },
109
- });
110
- ```
111
-
112
- ### 3.3 Custom Error Classes
113
- ```js
114
- import { HttpError } from 'webspresso';
115
-
116
- export class PaymentRequiredError extends HttpError {
117
- constructor(message = 'Payment Required', options = {}) {
118
- super(402, message, {
119
- code: options.code || 'PAYMENT_REQUIRED',
120
- expose: true,
121
- ...options,
122
- });
123
- }
124
- }
125
- ```
126
-
127
- ### 3.4 Custom Error Handler (`app.setErrorHandler`)
128
- ```js
129
- const { app } = createApp({ pagesDir: './pages' });
130
-
131
- app.setErrorHandler(async (err, req, res, next) => {
132
- if (err instanceof ValidationError) {
133
- return res.status(422).json({
134
- success: false,
135
- errors: err.fields,
136
- });
137
- }
138
- // Re-throwing or returning unhandled delegates to Webspresso default handler
139
- });
140
- ```
141
-
142
- ---
143
-
144
- ## 4. Response Contract & Production Masking
145
-
146
- ### Default JSON Contract:
147
- ```json
148
- {
149
- "status": 404,
150
- "error": "Not Found",
151
- "message": "User not found",
152
- "code": "USER_NOT_FOUND"
153
- }
154
- ```
155
-
156
- ### Development vs Production:
157
- - **Production (`NODE_ENV === 'production'`)**: 500 errors are masked to `"Internal Server Error"`. `stack`, `cause`, and internal file paths are never exposed to clients.
158
- - **Development**: Returns `stack`, `cause`, and a structured `trace` object identifying the exact route, module, source file, and execution phase.
159
-
160
- ---
161
-
162
- ## 5. Route Tracing & Request ID Observability
163
-
164
- Webspresso automatically correlates every request and enriches thrown errors with route-level execution metadata:
165
-
166
- ### 5.1 Request Correlation ID (`req.id` / `X-Request-Id`)
167
- Every incoming request is assigned a unique UUID (`req.id`) available on `req.id` and returned in the `X-Request-Id` response header:
168
- ```javascript
169
- // Access in services, loaders, or logs:
170
- const reqId = req.id; // e.g. "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
171
- ```
172
-
173
- ### 5.2 Context-Enriched Trace Object (Development Mode)
174
- When an exception occurs during request execution in `definePage`, `defineApi`, or a feature module, the development JSON payload contains an explicit `trace` object:
175
-
176
- ```json
177
- {
178
- "status": 500,
179
- "error": "Internal Server Error",
180
- "message": "Database query failed",
181
- "trace": {
182
- "route": "/api/auth/me",
183
- "method": "GET",
184
- "source": "src/modules/auth/api/me.get.js",
185
- "module": "auth",
186
- "phase": "handler",
187
- "requestId": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
188
- },
189
- "stack": "Error: Database query failed\n at ...",
190
- "cause": null
191
- }
192
- ```
193
-
194
- ### 5.3 Visual Console Error Banners
195
- In server logs, 500 errors are automatically highlighted with visual banners pinpointing the root cause:
196
- ```txt
197
- [webspresso] 🚨 Error at GET /api/auth/me [module: auth] (reqId: 9b1deb4d-3b7d-...)
198
- Source: src/modules/auth/api/me.get.js
199
- Phase: handler
200
- ```
201
-
202
- ---
203
-
204
- ## 6. Directory-Level Error Boundaries (`_error.njk` & `_error.js`)
205
-
206
- In addition to API JSON errors and the top-level 500 error page, Webspresso provides **Directory-Level Error Boundaries** for SSR pages (similar to Next.js `error.js` or Nuxt `error.vue`):
207
-
208
- ```
209
- pages/
210
- ├── _layout.njk # Root site layout
211
- ├── _error.njk # Global site error fallback
212
- └── dashboard/
213
- ├── _layout.njk # Dashboard layout (sidebar, header)
214
- ├── _error.njk # Dashboard-scoped error boundary
215
- ├── _error.js # Optional companion loader
216
- ├── billing/
217
- │ └── invoices.njk # If load() fails here...
218
- └── settings.njk
219
- ```
220
-
221
- ### 6.1 Isolated Error Containment
222
- When an unhandled error is thrown in a page route's `load()`, template rendering, or lifecycle hooks:
223
- 1. Webspresso searches up the directory hierarchy (innermost leaf to root `pages/`) for the nearest `_error.njk`.
224
- 2. The error boundary is rendered **inside** the ancestor cascading layouts (`_layout.njk`), ensuring that headers, sidebars, and navigation remain visible while displaying the isolated error state.
225
- 3. The HTTP response status is preserved from `error.status || error.statusCode || 500` (e.g. throwing `NotFoundError` sets HTTP 404).
226
-
227
- ### 6.2 Companion Loader (`_error.js`)
228
- An optional companion `_error.js` can prefetch support data, report errors to monitoring services, or enrich the context:
229
-
230
- ```javascript
231
- // pages/dashboard/_error.js
232
- module.exports = {
233
- load: async ({ error, req, res, ctx, db }) => {
234
- return {
235
- supportEmail: 'support@example.com',
236
- incidentId: req.id,
237
- };
238
- },
239
- };
240
- ```
241
-
242
- ### 6.3 Template Context Variables
243
- Inside `_error.njk`, the following variables are injected:
244
- - `error`: `{ message, status, statusCode, code, name, details, fields, stack }` (stack is only exposed when `isDev === true`).
245
- - `route`: `{ routePath, file }`.
246
- - `req`: `{ path, query, params, method, url }`.
247
- - `fsy`: Template helper suite.
248
- - Plus any data returned by companion `_error.js` loader.
249
-
250
- ### 6.4 Layout Opt-Out
251
- To render a full-page custom error view without the parent layout, set `layout: false` in `_error.js` or frontmatter:
252
- ```yaml
253
- ---
254
- layout: false
255
- ---
256
- <div class="standalone-error-screen">
257
- <h1>{{ error.status }} - {{ error.message }}</h1>
258
- </div>
259
- ```
260
-
package/.agents/EVENTS.md DELETED
@@ -1,179 +0,0 @@
1
- # Webspresso Events & Signals Architecture
2
-
3
- [← Back to AGENTS.md](.agents/AGENTS.md)
4
-
5
- Webspresso provides an enterprise, zero-dependency **Decoupled Event & Signals System** (`core/events`), combining **AdonisJS Events** (wildcard matching, async parallel/serial/waterfall dispatch, queue bridging, and listener auto-discovery) with **Django Signals** (declarative `Signal` channels, sender filtering, and ORM model signals).
6
-
7
- ---
8
-
9
- ## 1. Overview & Key Capabilities
10
-
11
- - **Zero External Dependencies**: Built entirely with native Node.js standard modules (`events`, `path`, `fs`).
12
- - **Synchronous & Asynchronous Emission**:
13
- - `events.emit(name, data)`: Fast sync/fire-and-forget dispatch.
14
- - `await events.emitAsync(name, data)`: Parallel concurrent execution (`Promise.allSettled`).
15
- - `await events.emitSerial(name, data)`: Sequential execution in priority order.
16
- - `await events.emitWaterfall(name, initialData)`: Sequential pipeline where output of listener $N$ feeds listener $N+1$.
17
- - **Wildcard & Pattern Matching**:
18
- - `events.on('order.*', (data, meta) => ...)`
19
- - `events.on('system.**', (data, meta) => ...)`
20
- - `events.onAny((eventName, data, meta) => ...)`
21
- - **Django-Style Signals & Sender Filtering**:
22
- - `const orderPlaced = defineSignal('order_placed')`
23
- - `orderPlaced.connect(receiver, { sender: 'Checkout' })`
24
- - `orderPlaced.send({ orderId: 101 }, { sender: 'Checkout' })`
25
- - **Background Queue Bridging**:
26
- - `events.listenAndQueue('user.created', 'send-welcome-email', { delay: 1000 })`
27
- - Automatically bridges emitted events directly to Webspresso's job queue (`core/queue`).
28
- - **ORM Model Signals Auto-Bridge**:
29
- - Automatically translates `ModelEvents` (`User.afterCreate`, `*.afterDelete`) to application events (`model:created`, `user:created`, `model:deleted`) with `sender: ctx.model`.
30
- - **Automatic Request & Context Decoration**:
31
- - Injected onto `req.events` on every Express request.
32
- - Available inside `req.context.events` for services: `ctx.events.emit('order.created', order)`.
33
- - Accessible on `app.events` and plugin contexts.
34
- - **Graceful Shutdown Integration**:
35
- - Cleaned up automatically on graceful shutdown via `ShutdownManager.onShutdown()`.
36
-
37
- ---
38
-
39
- ## 2. Using Events in API Routes & Services
40
-
41
- ### 2.1 Emitting Events in File-Based Routes (`pages/api/...`)
42
-
43
- ```javascript
44
- // pages/api/orders.post.js
45
- const { defineApi } = require('webspresso');
46
-
47
- module.exports = defineApi({
48
- schema: ({ z }) => ({
49
- body: z.object({
50
- total: z.number().positive(),
51
- items: z.array(z.string()),
52
- }),
53
- }),
54
- async handler(req, res) {
55
- const order = await req.db.getRepository('Order').create(req.input.body);
56
-
57
- // Synchronous fire-and-forget or awaited async parallel
58
- req.events.emit('order.created', order, { sender: 'Order' });
59
- // or: await req.events.emitAsync('order.created', order);
60
-
61
- return res.status(201).json(order);
62
- },
63
- });
64
- ```
65
-
66
- ### 2.2 Emitting Events inside Services (`services/...`)
67
-
68
- ```javascript
69
- // services/order/checkout.js
70
- const { defineService } = require('webspresso');
71
-
72
- module.exports = defineService({
73
- transaction: true,
74
- async handle(input, ctx) {
75
- const order = await ctx.db.getRepository('Order').create(input);
76
-
77
- // ctx.events is automatically available
78
- ctx.events.emit('order.checkout.completed', { orderId: order.id, user: ctx.req?.user });
79
-
80
- return order;
81
- },
82
- });
83
- ```
84
-
85
- ---
86
-
87
- ## 3. Registering Listeners
88
-
89
- ### 3.1 Programmatic Subscriptions
90
-
91
- ```javascript
92
- const { events } = require('webspresso');
93
-
94
- // 1. Basic Listener
95
- const unsub = events.on('order.created', (order, meta) => {
96
- console.log(`Order ${order.id} received at ${meta.timestamp}`);
97
- });
98
-
99
- // 2. Wildcard Listener
100
- events.on('order.*', (data, meta) => {
101
- console.log(`Order event ${meta.event} occurred`);
102
- });
103
-
104
- // 3. One-time Listener
105
- events.once('app.ready', () => {
106
- console.log('Application initialized');
107
- });
108
-
109
- // 4. Priority Ordering (higher runs first)
110
- events.on('user.login', auditLogin, { priority: 100 });
111
- events.on('user.login', notifyFriends, { priority: 0 });
112
- ```
113
-
114
- ### 3.2 File-Based Discovery (`listeners/` directory)
115
-
116
- Webspresso auto-discovers listeners in the `listeners/` root directory:
117
-
118
- ```javascript
119
- // listeners/send-welcome-email.js
120
- const { defineListener } = require('webspresso');
121
-
122
- module.exports = defineListener('user.created', async (user, meta) => {
123
- console.log(`Sending welcome email to ${user.email}`);
124
- });
125
- ```
126
-
127
- Or multiple listeners in a single file:
128
-
129
- ```javascript
130
- // listeners/audit-events.js
131
- module.exports = [
132
- {
133
- event: 'order.*',
134
- priority: 50,
135
- handle: async (data, meta) => {
136
- console.log(`Audit: ${meta.event}`, data);
137
- },
138
- },
139
- ];
140
- ```
141
-
142
- ---
143
-
144
- ## 4. Django-Style Signals
145
-
146
- For decoupled domain events with strict sender boundaries:
147
-
148
- ```javascript
149
- const { defineSignal } = require('webspresso');
150
-
151
- // Define signal
152
- const invoicePaid = defineSignal('invoice_paid');
153
-
154
- // Connect receiver with sender filter
155
- invoicePaid.connect(
156
- (invoice) => {
157
- console.log(`Invoice ${invoice.id} processed for subscription`);
158
- },
159
- { sender: 'Subscription' }
160
- );
161
-
162
- // Trigger signal
163
- invoicePaid.send({ id: 'INV-100' }, { sender: 'Subscription' });
164
- ```
165
-
166
- ---
167
-
168
- ## 5. Background Queue Bridging (`listenAndQueue`)
169
-
170
- Connect application events directly to background jobs without manual boilerplate:
171
-
172
- ```javascript
173
- // In plugin or server setup:
174
- app.events.listenAndQueue('order.created', 'send-order-confirmation', {
175
- delay: 2000,
176
- attempts: 3,
177
- });
178
- ```
179
- When `order.created` is emitted, Webspresso automatically calls `req.queue.dispatch('send-order-confirmation', order, options)`.
package/.agents/FLAGS.md DELETED
@@ -1,118 +0,0 @@
1
- # Feature Flags & Canary Traffic Splitter (`core/flags`)
2
-
3
- Webspresso features an enterprise-grade, zero-dependency **Feature Flag & Canary Traffic Splitting Engine** in `core/flags/`. It enables gradual rollouts, multivariate A/B testing, user/role/domain targeting, and runtime overrides across Nunjucks SSR templates, API endpoints (`defineApi`), service layers, and Edge/Hono adapters.
4
-
5
- ---
6
-
7
- ## 🎯 Key Capabilities
8
-
9
- 1. **Sub-Microsecond Deterministic Bucketing**:
10
- - FNV-1a 32-bit hashing algorithm (`getBucket`) maps user identifiers to deterministic integer buckets `0 .. 99` across distributed nodes without network calls.
11
- 2. **Flexible Targeting Rules**:
12
- - **Boolean Toggles**: `enabled: true | false`.
13
- - **Canary Percentage Rollout**: `rollout: 10` (10% of traffic, consistent per user).
14
- - **Targeted Whitelists**: `users: ['user_123', 'admin@corp.com']`.
15
- - **Role-Based Targeting**: `roles: ['admin', 'beta_tester']`.
16
- - **Email Domain Targeting**: `emailDomains: ['acme.com']`.
17
- - **Multivariate A/B Variants**: `variants: [{ name: 'blue_button', weight: 50 }, { name: 'green_button', weight: 50 }]`.
18
- - **Custom Predicates**: `condition: (ctx) => ctx.plan === 'enterprise'`.
19
- 3. **Multi-Layer Integration**:
20
- - **Express / Hono Middleware**: Automatically injects `req.flags`, `req.hasFeature()`, and `req.getVariant()`.
21
- - **Nunjucks SSR Templates**: Access via `{{ fsy.hasFeature('new_ui') }}` and `{{ fsy.flag('theme') }}`.
22
- - **Declarative API Route Guard**: `defineApi({ flag: 'beta_feature' })` returns HTTP 404 (or configured status) if disabled.
23
- - **Environment Variable Overrides**: Instant runtime overrides via `FEATURE_<FLAG_NAME>=true|false`.
24
-
25
- ---
26
-
27
- ## 🚀 Quick Start
28
-
29
- ### 1. Application Configuration
30
-
31
- ```javascript
32
- const { createApp } = require('webspresso');
33
-
34
- const app = createApp({
35
- flags: {
36
- // 1. Simple toggle
37
- dark_mode: true,
38
-
39
- // 2. 20% Canary rollout
40
- new_checkout: {
41
- rollout: 20,
42
- },
43
-
44
- // 3. User & Role targeting
45
- ai_assistant: {
46
- roles: ['pro', 'admin'],
47
- users: ['vip_client_1'],
48
- },
49
-
50
- // 4. Multivariate A/B Test
51
- pricing_page_test: {
52
- variants: [
53
- { name: 'control', weight: 50 },
54
- { name: 'discount_banner', weight: 50 },
55
- ],
56
- },
57
- },
58
- });
59
- ```
60
-
61
- ### 2. Nunjucks SSR Templates (`pages/*.njk`)
62
-
63
- ```nunjucks
64
- {% if fsy.hasFeature('new_checkout') %}
65
- {% include "components/new-checkout.njk" %}
66
- {% else %}
67
- {% include "components/legacy-checkout.njk" %}
68
- {% endif %}
69
-
70
- {% if fsy.flag('pricing_page_test') == 'discount_banner' %}
71
- <div class="banner">Special 20% Discount Applied!</div>
72
- {% endif %}
73
- ```
74
-
75
- ### 3. File-Based API Endpoints (`pages/api/...`)
76
-
77
- ```javascript
78
- const { defineApi } = require('webspresso');
79
-
80
- module.exports = defineApi({
81
- // Automatically blocks request with HTTP 404 if flag is disabled
82
- flag: 'ai_assistant',
83
- handler: async (req, res, ctx) => {
84
- // Also accessible inside ctx or req
85
- const isVip = ctx.hasFeature('vip_access');
86
- return res.json({ success: true, isVip });
87
- },
88
- });
89
- ```
90
-
91
- ### 4. Route Guard Middleware
92
-
93
- ```javascript
94
- const { createFeatureFlagManager } = require('webspresso');
95
-
96
- const flags = createFeatureFlagManager({
97
- flags: { secret_admin: { roles: ['admin'] } },
98
- });
99
-
100
- // Use as standalone route middleware
101
- app.get('/admin/secret', flags.guard('secret_admin', { statusCode: 403 }), (req, res) => {
102
- res.send('Welcome, Admin!');
103
- });
104
- ```
105
-
106
- ---
107
-
108
- ## ⚙️ Environment Overrides
109
-
110
- Flags can be overridden at runtime without code deployment or restart:
111
-
112
- ```bash
113
- # Enable flag
114
- FEATURE_NEW_CHECKOUT=true npm start
115
-
116
- # Disable flag
117
- FEATURE_NEW_CHECKOUT=false npm start
118
- ```
package/.agents/HEALTH.md DELETED
@@ -1,118 +0,0 @@
1
- # Cloud & Kubernetes Health & Readiness Probes (`core/health`)
2
-
3
- Webspresso includes a zero-dependency, Docker & Kubernetes-native health inspection system. It provides standard liveness (`/healthz`), readiness (`/readyz`), and combined diagnostics endpoints with automatic database pings, background worker queue checks, event loop delay metrics, and instant graceful shutdown draining integration.
4
-
5
- ---
6
-
7
- ## 🎯 Key Capabilities
8
-
9
- 1. **Kubernetes Liveness Probe (`/healthz`)**:
10
- - Confirms process vitality and responsiveness.
11
- - Gathers memory footprint (`rss`, `heapUsed`, `heapTotal` formatted and raw bytes), process uptime, PID, platform, and event loop lag statistics (`meanMs`, `maxMs`, `p99Ms`).
12
- - If event loop delay exceeds threshold (`eventLoopThresholdMs`), flags status as `degraded`.
13
-
14
- 2. **Kubernetes Readiness Probe (`/readyz`)**:
15
- - Confirms the container is fully prepared to handle live client traffic.
16
- - **Database Connectivity**: Pings active Knex instance (`SELECT 1`) with timeout protection.
17
- - **Queue Readiness**: Probes worker queue instance status.
18
- - **Custom Probes**: Executes user-registered async checks concurrently (`checks: { redis, stripe }`).
19
- - Returns `HTTP 200` (`{ ready: true, status: 'ready' }`) when all components are healthy.
20
- - Returns `HTTP 503` (`{ ready: false, status: 'unhealthy', checks: ... }`) if any component fails.
21
-
22
- 3. **Graceful Draining Integration (`ShutdownManager`)**:
23
- - As soon as the server enters shutdown draining (`app.close()` or `SIGTERM`/`SIGINT`), `/readyz` immediately returns `HTTP 503` (`{ ready: false, status: 'draining' }`).
24
- - Cloud load balancers (AWS ALB, K8s Ingress, Google Cloud, Cloudflare) immediately stop routing new traffic to this instance, while existing active requests drain cleanly without dropping a single connection.
25
-
26
- ---
27
-
28
- ## 🚀 Configuration Examples
29
-
30
- ### 1. Simple Enablement
31
-
32
- ```javascript
33
- const { createApp } = require('webspresso');
34
-
35
- const app = createApp({
36
- pagesDir: 'pages',
37
- // Enables /healthz (liveness), /readyz (readiness), and /health (combined)
38
- healthCheck: true,
39
- });
40
- ```
41
-
42
- Shorthand custom path:
43
- ```javascript
44
- const app = createApp({
45
- pagesDir: 'pages',
46
- healthCheck: '/api/status', // Mounts combined status on /api/status
47
- });
48
- ```
49
-
50
- ### 2. Full Production Configuration
51
-
52
- ```javascript
53
- const app = createApp({
54
- pagesDir: 'pages',
55
- db: myDatabaseInstance,
56
- queue: myQueueInstance,
57
- healthCheck: {
58
- enabled: true,
59
- path: '/healthz', // Liveness probe URL
60
- readyPath: '/readyz', // Readiness probe URL
61
- combinedPath: '/health', // Diagnostic summary URL
62
- db: true, // Automatically probe database (SELECT 1)
63
- queue: true, // Automatically probe worker queue
64
- eventLoopLag: true, // Monitor event loop delay
65
- eventLoopThresholdMs: 300, // Lag threshold in ms
66
- timeoutMs: 2500, // Probe timeout before marking down
67
- checks: { // Custom dependency probes
68
- redis: async () => redisClient.ping(),
69
- stripe: async () => checkStripeHealth(),
70
- },
71
- headers: {
72
- 'Cache-Control': 'no-store, no-cache, must-revalidate',
73
- 'X-Content-Type-Options': 'nosniff',
74
- },
75
- },
76
- });
77
- ```
78
-
79
- ---
80
-
81
- ## ☸️ Kubernetes Pod Spec Example
82
-
83
- ```yaml
84
- livenessProbe:
85
- httpGet:
86
- path: /healthz
87
- port: 3000
88
- initialDelaySeconds: 5
89
- periodSeconds: 10
90
- timeoutSeconds: 3
91
- failureThreshold: 3
92
-
93
- readinessProbe:
94
- httpGet:
95
- path: /readyz
96
- port: 3000
97
- initialDelaySeconds: 2
98
- periodSeconds: 5
99
- timeoutSeconds: 2
100
- failureThreshold: 2
101
- ```
102
-
103
- ---
104
-
105
- ## 🔍 Programmatic Inspection
106
-
107
- Access health state directly in code or CLI scripts:
108
-
109
- ```javascript
110
- // Get complete diagnostic payload
111
- const status = await app.getHealthStatus();
112
- console.log('Liveness:', status.liveness);
113
- console.log('Readiness:', status.readiness);
114
-
115
- // Or via healthManager
116
- const liveness = await app.healthManager.getLiveness();
117
- const readiness = await app.healthManager.getReadiness();
118
- ```