webspresso 0.0.89 → 0.0.91

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 (102) hide show
  1. package/README.md +450 -53
  2. package/bin/commands/service.js +193 -0
  3. package/bin/webspresso.js +2 -0
  4. package/core/auth/manager.js +36 -26
  5. package/core/auth/middleware.js +21 -2
  6. package/core/compression/compressible.js +51 -0
  7. package/core/compression/compression-stream.js +59 -0
  8. package/core/compression/index.js +25 -0
  9. package/core/compression/middleware.js +301 -0
  10. package/core/compression/negotiate.js +153 -0
  11. package/core/errors/base.js +31 -0
  12. package/core/errors/domain.js +193 -0
  13. package/core/errors/http.js +133 -0
  14. package/core/errors/index.js +77 -0
  15. package/core/errors/middleware.js +162 -0
  16. package/core/errors/normalize.js +171 -0
  17. package/core/orm/complexity.js +120 -0
  18. package/core/orm/events.js +64 -14
  19. package/core/orm/index.js +45 -10
  20. package/core/orm/model.js +10 -2
  21. package/core/orm/query-builder.js +30 -4
  22. package/core/orm/repository.js +57 -25
  23. package/core/orm/scopes.js +2 -1
  24. package/core/orm/transaction.js +39 -5
  25. package/core/orm/utils.js +6 -3
  26. package/core/queue/adapters/database.js +284 -0
  27. package/core/queue/adapters/memory.js +155 -0
  28. package/core/queue/adapters/redis.js +336 -0
  29. package/core/queue/index.js +21 -0
  30. package/core/queue/job.js +103 -0
  31. package/core/queue/manager.js +387 -0
  32. package/core/realtime/adapters/redis.js +431 -0
  33. package/core/realtime/adapters/socket-io.js +203 -0
  34. package/core/realtime/adapters/sse.js +210 -0
  35. package/core/realtime/adapters/websocket.js +337 -0
  36. package/core/realtime/auth-manager.js +140 -0
  37. package/core/realtime/core.js +359 -0
  38. package/core/realtime/emitter.js +98 -0
  39. package/core/realtime/errors.js +71 -0
  40. package/core/realtime/index.js +79 -0
  41. package/core/realtime/reconnect-manager.js +118 -0
  42. package/core/realtime/registry.js +243 -0
  43. package/core/shutdown/http-adapter.js +134 -0
  44. package/core/shutdown/index.js +12 -0
  45. package/core/shutdown/shutdown-manager.js +312 -0
  46. package/core/ssr/index.js +13 -0
  47. package/core/ssr/stream.js +174 -0
  48. package/index.d.ts +829 -52
  49. package/index.js +103 -0
  50. package/package.json +22 -2
  51. package/plugins/admin-panel/api.js +37 -4
  52. package/plugins/admin-panel/app.js +67 -17
  53. package/plugins/admin-panel/client/parts/01-state-api-breadcrumb.js +4 -1
  54. package/plugins/admin-panel/client/parts/03-pagination-intro.js +25 -18
  55. package/plugins/admin-panel/client/parts/04-field-renderers.js +102 -58
  56. package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +37 -23
  57. package/plugins/admin-panel/client/parts/08-record-list.js +27 -34
  58. package/plugins/admin-panel/client/parts/09-record-form.js +168 -19
  59. package/plugins/admin-panel/core/admin-module.js +2 -2
  60. package/plugins/admin-panel/core/api-extensions.js +149 -0
  61. package/plugins/admin-panel/field-renderers/rich-text.js +3 -2
  62. package/plugins/admin-panel/index.js +131 -18
  63. package/plugins/admin-panel/modules/admin-users.js +770 -0
  64. package/plugins/admin-panel/modules/custom-pages.js +144 -45
  65. package/plugins/admin-panel/modules/menu.js +85 -0
  66. package/plugins/admin-panel/modules/user-management.js +5 -2
  67. package/plugins/admin-panel/vendor/mithril.min.js +1 -0
  68. package/plugins/admin-panel/vendor/quill.min.js +7 -0
  69. package/plugins/admin-panel/vendor/quill.snow.min.css +945 -0
  70. package/plugins/admin-panel/vendor/tailwind.min.js +65 -0
  71. package/plugins/basic-auth/index.js +293 -0
  72. package/plugins/content/client/inline-edit.js +41 -17
  73. package/plugins/cors.js +12 -8
  74. package/plugins/csrf/index.js +8 -8
  75. package/plugins/data-exchange/export-xlsx.js +5 -1
  76. package/plugins/data-exchange/import.js +16 -2
  77. package/plugins/data-exchange/index.js +14 -0
  78. package/plugins/email/index.js +14 -1
  79. package/plugins/index.js +29 -0
  80. package/plugins/queue/index.js +108 -0
  81. package/plugins/realtime/index.js +150 -0
  82. package/plugins/recaptcha/index.js +11 -0
  83. package/plugins/redirect/index.js +9 -1
  84. package/plugins/rest-resources/index.js +280 -11
  85. package/plugins/upload/index.js +16 -0
  86. package/src/app-context.js +46 -6
  87. package/src/file-router.js +220 -28
  88. package/src/helpers.js +24 -5
  89. package/src/plugin-manager.js +134 -7
  90. package/src/server.js +470 -44
  91. package/src/services/builtins/auth.js +273 -0
  92. package/src/services/builtins/exchange.js +229 -0
  93. package/src/services/builtins/mail.js +134 -0
  94. package/src/services/builtins/media.js +196 -0
  95. package/src/services/builtins/system.js +221 -0
  96. package/src/services/discovery.js +140 -0
  97. package/src/services/executor.js +292 -0
  98. package/src/services/index.js +70 -0
  99. package/src/services/memoize.js +256 -0
  100. package/src/services/registry.js +288 -0
  101. package/src/services/validator.js +281 -0
  102. package/templates/skills/webspresso-usage/REFERENCE-framework.md +2 -2
@@ -123,20 +123,37 @@ class PluginManager {
123
123
  this.routes = []; // Collected route metadata
124
124
  this.customRoutes = []; // Routes added by plugins
125
125
  this.cspDirectives = new Map(); // directive -> Set of sources (from plugins)
126
+ this.disposers = []; // Array<{ name: string, fn: Function }>
126
127
  this.app = null;
127
128
  this.nunjucksEnv = null;
129
+ this.shutdownManager = null;
130
+ }
131
+
132
+ /**
133
+ * Register a plugin disposer
134
+ * @param {string} name
135
+ * @param {Function} fn
136
+ */
137
+ registerDisposer(name, fn) {
138
+ if (typeof fn === 'function') {
139
+ this.disposers.push({ name: name || 'plugin', fn });
140
+ if (this.shutdownManager && typeof this.shutdownManager.registerDisposer === 'function') {
141
+ this.shutdownManager.registerDisposer(name, fn);
142
+ }
143
+ }
128
144
  }
129
145
 
130
146
  /**
131
147
  * Register plugins with the manager (async version)
132
148
  * @param {Array} plugins - Array of plugin definitions or factory functions
133
- * @param {Object} context - Context object `{ app, nunjucksEnv, options, middlewares? }`
149
+ * @param {Object} context - Context object `{ app, nunjucksEnv, options, middlewares?, shutdownManager? }`
134
150
  */
135
151
  async register(plugins, context) {
136
152
  if (!plugins || !Array.isArray(plugins)) return;
137
153
 
138
154
  this.app = context.app;
139
155
  this.nunjucksEnv = context.nunjucksEnv;
156
+ this.shutdownManager = context.shutdownManager || null;
140
157
 
141
158
  // Normalize plugins (handle factory functions)
142
159
  const normalizedPlugins = plugins.map(p => {
@@ -159,13 +176,14 @@ class PluginManager {
159
176
  /**
160
177
  * Register plugins with the manager (sync version)
161
178
  * @param {Array} plugins - Array of plugin definitions or factory functions
162
- * @param {Object} context - Context object `{ app, nunjucksEnv, options, middlewares? }`
179
+ * @param {Object} context - Context object `{ app, nunjucksEnv, options, middlewares?, shutdownManager? }`
163
180
  */
164
181
  registerSync(plugins, context) {
165
182
  if (!plugins || !Array.isArray(plugins)) return;
166
183
 
167
184
  this.app = context.app;
168
185
  this.nunjucksEnv = context.nunjucksEnv;
186
+ this.shutdownManager = context.shutdownManager || null;
169
187
 
170
188
  // Normalize plugins (handle factory functions)
171
189
  const normalizedPlugins = plugins.map(p => {
@@ -202,7 +220,10 @@ class PluginManager {
202
220
  continue;
203
221
  }
204
222
  pluginMap.set(plugin.name, plugin);
205
- graph.set(plugin.name, new Set(Object.keys(plugin.dependencies || {})));
223
+ const depNames = Array.isArray(plugin.dependencies)
224
+ ? plugin.dependencies
225
+ : Object.keys(plugin.dependencies || {});
226
+ graph.set(plugin.name, new Set(depNames));
206
227
  }
207
228
 
208
229
  // Topological sort (Kahn's algorithm)
@@ -273,10 +294,32 @@ class PluginManager {
273
294
  this._collectCspDirectives(plugin.csp);
274
295
  }
275
296
 
297
+ let hasDisposer = false;
298
+
299
+ // Call setup hook if provided
300
+ if (typeof plugin.setup === 'function') {
301
+ try {
302
+ const result = await plugin.setup(ctx.app || ctx);
303
+ if (typeof result === 'function') {
304
+ this.registerDisposer(plugin.name, result);
305
+ hasDisposer = true;
306
+ }
307
+ } catch (err) {
308
+ console.warn(`[plugin-manager] Plugin "${plugin.name}" setup() failed:`, err.message);
309
+ this.plugins.delete(plugin.name);
310
+ if (plugin.api) this.pluginAPIs.delete(plugin.name);
311
+ return;
312
+ }
313
+ }
314
+
276
315
  // Call register hook
277
316
  if (typeof plugin.register === 'function') {
278
317
  try {
279
- await plugin.register(ctx);
318
+ const result = await plugin.register(ctx);
319
+ if (typeof result === 'function') {
320
+ this.registerDisposer(plugin.name, result);
321
+ hasDisposer = true;
322
+ }
280
323
  } catch (err) {
281
324
  console.warn(`[plugin-manager] Plugin "${plugin.name}" register() failed:`, err.message);
282
325
  this.plugins.delete(plugin.name);
@@ -285,6 +328,15 @@ class PluginManager {
285
328
  }
286
329
  }
287
330
 
331
+ // Support explicit shutdown or dispose methods if not already registered
332
+ if (!hasDisposer) {
333
+ if (typeof plugin.shutdown === 'function') {
334
+ this.registerDisposer(plugin.name, () => plugin.shutdown(ctx.app || ctx));
335
+ } else if (typeof plugin.dispose === 'function') {
336
+ this.registerDisposer(plugin.name, () => plugin.dispose(ctx));
337
+ }
338
+ }
339
+
288
340
  // Apply registered helpers to nunjucks
289
341
  this._applyHelpersAndFilters();
290
342
  }
@@ -317,10 +369,32 @@ class PluginManager {
317
369
  this._collectCspDirectives(plugin.csp);
318
370
  }
319
371
 
372
+ let hasDisposer = false;
373
+
374
+ // Call setup hook if provided
375
+ if (typeof plugin.setup === 'function') {
376
+ try {
377
+ const result = plugin.setup(ctx.app || ctx);
378
+ if (typeof result === 'function') {
379
+ this.registerDisposer(plugin.name, result);
380
+ hasDisposer = true;
381
+ }
382
+ } catch (err) {
383
+ console.warn(`[plugin-manager] Plugin "${plugin.name}" setup() failed:`, err.message);
384
+ this.plugins.delete(plugin.name);
385
+ if (plugin.api) this.pluginAPIs.delete(plugin.name);
386
+ return;
387
+ }
388
+ }
389
+
320
390
  // Call register hook (sync - if plugin has async register, it won't wait)
321
391
  if (typeof plugin.register === 'function') {
322
392
  try {
323
- plugin.register(ctx);
393
+ const result = plugin.register(ctx);
394
+ if (typeof result === 'function') {
395
+ this.registerDisposer(plugin.name, result);
396
+ hasDisposer = true;
397
+ }
324
398
  } catch (err) {
325
399
  console.warn(`[plugin-manager] Plugin "${plugin.name}" register() failed:`, err.message);
326
400
  this.plugins.delete(plugin.name);
@@ -329,6 +403,15 @@ class PluginManager {
329
403
  }
330
404
  }
331
405
 
406
+ // Support explicit shutdown or dispose methods if not already registered
407
+ if (!hasDisposer) {
408
+ if (typeof plugin.shutdown === 'function') {
409
+ this.registerDisposer(plugin.name, () => plugin.shutdown(ctx.app || ctx));
410
+ } else if (typeof plugin.dispose === 'function') {
411
+ this.registerDisposer(plugin.name, () => plugin.dispose(ctx));
412
+ }
413
+ }
414
+
332
415
  // Apply registered helpers to nunjucks
333
416
  this._applyHelpersAndFilters();
334
417
  }
@@ -339,7 +422,11 @@ class PluginManager {
339
422
  _validateDependencies(plugin) {
340
423
  if (!plugin.dependencies) return;
341
424
 
342
- for (const [depName, versionRange] of Object.entries(plugin.dependencies)) {
425
+ const depEntries = Array.isArray(plugin.dependencies)
426
+ ? plugin.dependencies.map(d => [d, '*'])
427
+ : Object.entries(plugin.dependencies);
428
+
429
+ for (const [depName, versionRange] of depEntries) {
343
430
  const dep = this.plugins.get(depName);
344
431
 
345
432
  if (!dep) {
@@ -350,7 +437,7 @@ class PluginManager {
350
437
  continue;
351
438
  }
352
439
 
353
- if (dep.version && !semver.satisfies(dep.version, versionRange)) {
440
+ if (dep.version && versionRange !== '*' && !semver.satisfies(dep.version, versionRange)) {
354
441
  console.warn(
355
442
  `[plugin-manager] Plugin "${plugin.name}" requires "${depName}@${versionRange}" ` +
356
443
  `but found v${dep.version}. Plugin may not work correctly.`
@@ -414,6 +501,22 @@ class PluginManager {
414
501
  // Script Injection API
415
502
  // ============================================
416
503
 
504
+ /**
505
+ * Register a cleanup / disposer hook for this plugin
506
+ * @param {Function} fn
507
+ */
508
+ onDispose(fn) {
509
+ self.registerDisposer(plugin.name, fn);
510
+ },
511
+
512
+ /**
513
+ * Alias for onDispose
514
+ * @param {Function} fn
515
+ */
516
+ onShutdown(fn) {
517
+ self.registerDisposer(plugin.name, fn);
518
+ },
519
+
417
520
  /**
418
521
  * Inject content into head section
419
522
  * @param {string} content - HTML/script content
@@ -576,6 +679,30 @@ class PluginManager {
576
679
  return this.plugins.has(name);
577
680
  }
578
681
 
682
+ /**
683
+ * Get all registered disposers
684
+ * @returns {Array<{ name: string, fn: Function }>}
685
+ */
686
+ getDisposers() {
687
+ return this.disposers.slice();
688
+ }
689
+
690
+ /**
691
+ * Execute all registered plugin disposers in reverse registration order
692
+ * @returns {Promise<void>}
693
+ */
694
+ async dispose() {
695
+ const reverseDisposers = this.disposers.slice().reverse();
696
+ for (const { name, fn } of reverseDisposers) {
697
+ try {
698
+ await fn();
699
+ } catch (err) {
700
+ console.warn(`[plugin-manager] Plugin "${name}" disposer failed:`, err.message);
701
+ }
702
+ }
703
+ this.disposers = [];
704
+ }
705
+
579
706
  /**
580
707
  * Get all registered plugin names
581
708
  */