webspresso 0.0.90 → 0.0.93
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 +98 -2509
- package/bin/commands/admin-password.js +8 -2
- package/bin/commands/api.js +7 -1
- package/bin/commands/favicon-generate.js +6 -4
- package/bin/commands/mcp.js +162 -0
- package/bin/commands/new.js +7 -1
- package/bin/commands/page.js +7 -1
- package/bin/commands/seed.js +7 -1
- package/bin/commands/service.js +199 -0
- package/bin/commands/skill.js +7 -1
- package/bin/webspresso.js +4 -0
- package/core/auth/middleware.js +9 -0
- package/core/compileSchema.js +1 -5
- package/core/compression/compressible.js +63 -0
- package/core/compression/compression-stream.js +59 -0
- package/core/compression/index.js +25 -0
- package/core/compression/middleware.js +310 -0
- package/core/compression/negotiate.js +165 -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/kernel/app.js +2 -1
- package/core/kernel/index.js +5 -1
- package/core/orm/cache/fingerprint.js +23 -5
- package/core/orm/complexity.js +120 -0
- package/core/orm/eager-loader.js +54 -9
- package/core/orm/events.js +64 -14
- package/core/orm/index.js +83 -12
- package/core/orm/model.js +13 -2
- package/core/orm/query-builder.js +30 -4
- package/core/orm/repository.js +57 -25
- package/core/orm/transaction.js +39 -5
- package/core/orm/utils.js +6 -3
- package/core/queue/adapters/database.js +317 -0
- package/core/queue/adapters/memory.js +176 -0
- package/core/queue/adapters/redis.js +357 -0
- package/core/queue/index.js +21 -0
- package/core/queue/job.js +103 -0
- package/core/queue/manager.js +413 -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/core/validation/index.js +70 -0
- package/index.d.ts +924 -53
- package/index.js +110 -1
- package/package.json +30 -2
- package/plugins/admin-panel/api.js +37 -4
- package/plugins/admin-panel/app.js +57 -17
- package/plugins/admin-panel/client/parts/01-state-api-breadcrumb.js +10 -2
- package/plugins/admin-panel/client/parts/04-field-renderers.js +102 -58
- package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +51 -27
- package/plugins/admin-panel/client/parts/07-model-list.js +9 -4
- package/plugins/admin-panel/client/parts/08-record-list.js +37 -26
- 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 +155 -23
- package/plugins/admin-panel/modules/bulk-actions.js +9 -2
- package/plugins/admin-panel/modules/custom-pages.js +144 -45
- package/plugins/admin-panel/modules/dashboard.js +8 -1
- 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/data-exchange/export-xlsx.js +28 -2
- package/plugins/data-exchange/import.js +55 -16
- package/plugins/data-exchange/index.js +19 -0
- package/plugins/data-exchange/parse-table.js +14 -5
- package/plugins/data-exchange/record-selection.js +2 -0
- package/plugins/email/index.js +14 -1
- package/plugins/index.js +33 -0
- package/plugins/mcp/auth.js +104 -0
- package/plugins/mcp/discovery.js +88 -0
- package/plugins/mcp/index.js +208 -0
- package/plugins/mcp/prompts.js +149 -0
- package/plugins/mcp/providers/orm.js +325 -0
- package/plugins/mcp/providers/services.js +190 -0
- package/plugins/mcp/providers/system.js +87 -0
- package/plugins/mcp/server.js +472 -0
- package/plugins/mcp/transports/sse.js +139 -0
- package/plugins/mcp/transports/stdio.js +108 -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 +366 -130
- package/src/helpers.js +20 -2
- package/src/njk-frontmatter.js +24 -14
- package/src/plugin-manager.js +124 -4
- package/src/server.js +332 -54
- 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 +141 -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 +290 -0
- package/src/services/validator.js +230 -0
- package/templates/skills/webspresso-usage/REFERENCE-framework.md +6 -4
package/src/njk-frontmatter.js
CHANGED
|
@@ -115,6 +115,24 @@ function readAndParse(absPath, isDevLike = false) {
|
|
|
115
115
|
* @param {boolean} isDev NODE_ENV !== 'production' style
|
|
116
116
|
*/
|
|
117
117
|
function loadNjkRouteTemplate(absPath, isDev) {
|
|
118
|
+
if (!isDev) {
|
|
119
|
+
const cached = prodRouteCache.get(absPath);
|
|
120
|
+
if (cached) {
|
|
121
|
+
return cached;
|
|
122
|
+
}
|
|
123
|
+
if (!fs.existsSync(absPath)) {
|
|
124
|
+
const empty = {
|
|
125
|
+
useStringRender: false,
|
|
126
|
+
templateBody: null,
|
|
127
|
+
metaPatch: {},
|
|
128
|
+
dataPatch: {},
|
|
129
|
+
};
|
|
130
|
+
prodRouteCache.set(absPath, empty);
|
|
131
|
+
return empty;
|
|
132
|
+
}
|
|
133
|
+
return readAndParse(absPath, false);
|
|
134
|
+
}
|
|
135
|
+
|
|
118
136
|
if (!fs.existsSync(absPath)) {
|
|
119
137
|
return {
|
|
120
138
|
useStringRender: false,
|
|
@@ -125,21 +143,13 @@ function loadNjkRouteTemplate(absPath, isDev) {
|
|
|
125
143
|
}
|
|
126
144
|
|
|
127
145
|
const stat = fs.statSync(absPath);
|
|
128
|
-
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
if (prev && prev.mtimeMs >= stat.mtimeMs) {
|
|
132
|
-
return prev.cached;
|
|
133
|
-
}
|
|
134
|
-
const fresh = readAndParse(absPath, true);
|
|
135
|
-
devRouteCache.set(absPath, { mtimeMs: stat.mtimeMs, cached: fresh });
|
|
136
|
-
return fresh;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (prodRouteCache.has(absPath)) {
|
|
140
|
-
return prodRouteCache.get(absPath);
|
|
146
|
+
const prev = devRouteCache.get(absPath);
|
|
147
|
+
if (prev && prev.mtimeMs >= stat.mtimeMs) {
|
|
148
|
+
return prev.cached;
|
|
141
149
|
}
|
|
142
|
-
|
|
150
|
+
const fresh = readAndParse(absPath, true);
|
|
151
|
+
devRouteCache.set(absPath, { mtimeMs: stat.mtimeMs, cached: fresh });
|
|
152
|
+
return fresh;
|
|
143
153
|
}
|
|
144
154
|
|
|
145
155
|
function clearNjkFrontmatterCaches() {
|
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 => {
|
|
@@ -276,10 +294,32 @@ class PluginManager {
|
|
|
276
294
|
this._collectCspDirectives(plugin.csp);
|
|
277
295
|
}
|
|
278
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
|
+
|
|
279
315
|
// Call register hook
|
|
280
316
|
if (typeof plugin.register === 'function') {
|
|
281
317
|
try {
|
|
282
|
-
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
|
+
}
|
|
283
323
|
} catch (err) {
|
|
284
324
|
console.warn(`[plugin-manager] Plugin "${plugin.name}" register() failed:`, err.message);
|
|
285
325
|
this.plugins.delete(plugin.name);
|
|
@@ -288,6 +328,15 @@ class PluginManager {
|
|
|
288
328
|
}
|
|
289
329
|
}
|
|
290
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
|
+
|
|
291
340
|
// Apply registered helpers to nunjucks
|
|
292
341
|
this._applyHelpersAndFilters();
|
|
293
342
|
}
|
|
@@ -320,10 +369,32 @@ class PluginManager {
|
|
|
320
369
|
this._collectCspDirectives(plugin.csp);
|
|
321
370
|
}
|
|
322
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
|
+
|
|
323
390
|
// Call register hook (sync - if plugin has async register, it won't wait)
|
|
324
391
|
if (typeof plugin.register === 'function') {
|
|
325
392
|
try {
|
|
326
|
-
plugin.register(ctx);
|
|
393
|
+
const result = plugin.register(ctx);
|
|
394
|
+
if (typeof result === 'function') {
|
|
395
|
+
this.registerDisposer(plugin.name, result);
|
|
396
|
+
hasDisposer = true;
|
|
397
|
+
}
|
|
327
398
|
} catch (err) {
|
|
328
399
|
console.warn(`[plugin-manager] Plugin "${plugin.name}" register() failed:`, err.message);
|
|
329
400
|
this.plugins.delete(plugin.name);
|
|
@@ -332,6 +403,15 @@ class PluginManager {
|
|
|
332
403
|
}
|
|
333
404
|
}
|
|
334
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
|
+
|
|
335
415
|
// Apply registered helpers to nunjucks
|
|
336
416
|
this._applyHelpersAndFilters();
|
|
337
417
|
}
|
|
@@ -421,6 +501,22 @@ class PluginManager {
|
|
|
421
501
|
// Script Injection API
|
|
422
502
|
// ============================================
|
|
423
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
|
+
|
|
424
520
|
/**
|
|
425
521
|
* Inject content into head section
|
|
426
522
|
* @param {string} content - HTML/script content
|
|
@@ -583,6 +679,30 @@ class PluginManager {
|
|
|
583
679
|
return this.plugins.has(name);
|
|
584
680
|
}
|
|
585
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
|
+
|
|
586
706
|
/**
|
|
587
707
|
* Get all registered plugin names
|
|
588
708
|
*/
|