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.
- package/README.md +450 -53
- package/bin/commands/service.js +193 -0
- package/bin/webspresso.js +2 -0
- package/core/auth/manager.js +36 -26
- package/core/auth/middleware.js +21 -2
- package/core/compression/compressible.js +51 -0
- package/core/compression/compression-stream.js +59 -0
- package/core/compression/index.js +25 -0
- package/core/compression/middleware.js +301 -0
- package/core/compression/negotiate.js +153 -0
- package/core/errors/base.js +31 -0
- package/core/errors/domain.js +193 -0
- package/core/errors/http.js +133 -0
- package/core/errors/index.js +77 -0
- package/core/errors/middleware.js +162 -0
- package/core/errors/normalize.js +171 -0
- package/core/orm/complexity.js +120 -0
- package/core/orm/events.js +64 -14
- package/core/orm/index.js +45 -10
- package/core/orm/model.js +10 -2
- package/core/orm/query-builder.js +30 -4
- package/core/orm/repository.js +57 -25
- package/core/orm/scopes.js +2 -1
- package/core/orm/transaction.js +39 -5
- package/core/orm/utils.js +6 -3
- package/core/queue/adapters/database.js +284 -0
- package/core/queue/adapters/memory.js +155 -0
- package/core/queue/adapters/redis.js +336 -0
- package/core/queue/index.js +21 -0
- package/core/queue/job.js +103 -0
- package/core/queue/manager.js +387 -0
- package/core/realtime/adapters/redis.js +431 -0
- package/core/realtime/adapters/socket-io.js +203 -0
- package/core/realtime/adapters/sse.js +210 -0
- package/core/realtime/adapters/websocket.js +337 -0
- package/core/realtime/auth-manager.js +140 -0
- package/core/realtime/core.js +359 -0
- package/core/realtime/emitter.js +98 -0
- package/core/realtime/errors.js +71 -0
- package/core/realtime/index.js +79 -0
- package/core/realtime/reconnect-manager.js +118 -0
- package/core/realtime/registry.js +243 -0
- package/core/shutdown/http-adapter.js +134 -0
- package/core/shutdown/index.js +12 -0
- package/core/shutdown/shutdown-manager.js +312 -0
- package/core/ssr/index.js +13 -0
- package/core/ssr/stream.js +174 -0
- package/index.d.ts +829 -52
- package/index.js +103 -0
- package/package.json +22 -2
- package/plugins/admin-panel/api.js +37 -4
- package/plugins/admin-panel/app.js +67 -17
- package/plugins/admin-panel/client/parts/01-state-api-breadcrumb.js +4 -1
- package/plugins/admin-panel/client/parts/03-pagination-intro.js +25 -18
- package/plugins/admin-panel/client/parts/04-field-renderers.js +102 -58
- package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +37 -23
- package/plugins/admin-panel/client/parts/08-record-list.js +27 -34
- package/plugins/admin-panel/client/parts/09-record-form.js +168 -19
- package/plugins/admin-panel/core/admin-module.js +2 -2
- package/plugins/admin-panel/core/api-extensions.js +149 -0
- package/plugins/admin-panel/field-renderers/rich-text.js +3 -2
- package/plugins/admin-panel/index.js +131 -18
- package/plugins/admin-panel/modules/admin-users.js +770 -0
- package/plugins/admin-panel/modules/custom-pages.js +144 -45
- package/plugins/admin-panel/modules/menu.js +85 -0
- package/plugins/admin-panel/modules/user-management.js +5 -2
- package/plugins/admin-panel/vendor/mithril.min.js +1 -0
- package/plugins/admin-panel/vendor/quill.min.js +7 -0
- package/plugins/admin-panel/vendor/quill.snow.min.css +945 -0
- package/plugins/admin-panel/vendor/tailwind.min.js +65 -0
- package/plugins/basic-auth/index.js +293 -0
- package/plugins/content/client/inline-edit.js +41 -17
- package/plugins/cors.js +12 -8
- package/plugins/csrf/index.js +8 -8
- package/plugins/data-exchange/export-xlsx.js +5 -1
- package/plugins/data-exchange/import.js +16 -2
- package/plugins/data-exchange/index.js +14 -0
- package/plugins/email/index.js +14 -1
- package/plugins/index.js +29 -0
- package/plugins/queue/index.js +108 -0
- package/plugins/realtime/index.js +150 -0
- package/plugins/recaptcha/index.js +11 -0
- package/plugins/redirect/index.js +9 -1
- package/plugins/rest-resources/index.js +280 -11
- package/plugins/upload/index.js +16 -0
- package/src/app-context.js +46 -6
- package/src/file-router.js +220 -28
- package/src/helpers.js +24 -5
- package/src/plugin-manager.js +134 -7
- package/src/server.js +470 -44
- package/src/services/builtins/auth.js +273 -0
- package/src/services/builtins/exchange.js +229 -0
- package/src/services/builtins/mail.js +134 -0
- package/src/services/builtins/media.js +196 -0
- package/src/services/builtins/system.js +221 -0
- package/src/services/discovery.js +140 -0
- package/src/services/executor.js +292 -0
- package/src/services/index.js +70 -0
- package/src/services/memoize.js +256 -0
- package/src/services/registry.js +288 -0
- package/src/services/validator.js +281 -0
- package/templates/skills/webspresso-usage/REFERENCE-framework.md +2 -2
package/src/plugin-manager.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
*/
|