webspresso 0.0.96 → 0.0.98

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 (121) hide show
  1. package/.agents/ADMIN.md +153 -5
  2. package/.agents/ARCHITECTURE.md +10 -0
  3. package/.agents/CLI.md +195 -1
  4. package/.agents/PLUGINS.md +153 -9
  5. package/.agents/QUEUE.md +43 -0
  6. package/.agents/skills/webspresso-admin-and-plugins/SKILL.md +122 -0
  7. package/.agents/skills/webspresso-framework/SKILL.md +56 -0
  8. package/.agents/skills/webspresso-orm-and-database/SKILL.md +119 -0
  9. package/.agents/skills/webspresso-routing-and-api/SKILL.md +114 -0
  10. package/.agents/skills/webspresso-services/SKILL.md +101 -0
  11. package/bin/commands/add-esbuild.js +80 -0
  12. package/bin/commands/add-tailwind.js +76 -31
  13. package/bin/commands/admin-password.js +2 -1
  14. package/bin/commands/admin-setup.js +1 -1
  15. package/bin/commands/agents-init.js +51 -11
  16. package/bin/commands/analytics-prune.js +57 -0
  17. package/bin/commands/api.js +207 -15
  18. package/bin/commands/audit-prune.js +1 -1
  19. package/bin/commands/build.js +99 -0
  20. package/bin/commands/dev.js +64 -35
  21. package/bin/commands/doctor.js +26 -0
  22. package/bin/commands/email-prune.js +47 -0
  23. package/bin/commands/favicon-generate.js +1 -1
  24. package/bin/commands/make-job.js +86 -0
  25. package/bin/commands/make-model.js +250 -0
  26. package/bin/commands/new.js +45 -28
  27. package/bin/commands/openapi.js +102 -0
  28. package/bin/commands/orm-map.js +5 -4
  29. package/bin/commands/page.js +6 -3
  30. package/bin/commands/polar-migrate.js +1 -1
  31. package/bin/commands/queue-prune.js +59 -0
  32. package/bin/commands/routes.js +52 -0
  33. package/bin/commands/seed.js +1 -1
  34. package/bin/commands/service.js +6 -3
  35. package/bin/commands/typegen.js +106 -0
  36. package/bin/commands/upload-prune.js +79 -0
  37. package/bin/webspresso.js +22 -0
  38. package/core/applySchema.js +13 -6
  39. package/core/auth/hash.js +31 -17
  40. package/core/compression/middleware.js +23 -17
  41. package/core/openapi/build-from-api-routes.js +160 -29
  42. package/core/orm/cache/fingerprint.js +15 -1
  43. package/core/orm/cache/memory-provider.js +27 -2
  44. package/core/orm/index.js +12 -7
  45. package/core/orm/model.js +49 -7
  46. package/core/queue/adapters/database.js +94 -0
  47. package/core/queue/adapters/memory.js +62 -0
  48. package/core/queue/adapters/redis.js +80 -0
  49. package/core/queue/manager.js +123 -0
  50. package/core/ssr/stream.js +11 -0
  51. package/index.d.ts +660 -10
  52. package/index.js +44 -96
  53. package/package.json +32 -12
  54. package/plugins/admin-panel/api.js +279 -17
  55. package/plugins/admin-panel/app.js +124 -31
  56. package/plugins/admin-panel/client/parts/01-state-api-breadcrumb.js +314 -9
  57. package/plugins/admin-panel/client/parts/04-field-renderers.js +264 -67
  58. package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +10 -5
  59. package/plugins/admin-panel/client/parts/06-login-setup-forms.js +119 -28
  60. package/plugins/admin-panel/client/parts/07-model-list.js +49 -8
  61. package/plugins/admin-panel/client/parts/08-record-list.js +243 -23
  62. package/plugins/admin-panel/client/parts/09-record-form.js +59 -12
  63. package/plugins/admin-panel/client/parts/10-export-registry.js +23 -0
  64. package/plugins/admin-panel/core/admin-module.js +13 -7
  65. package/plugins/admin-panel/core/api-extensions.js +34 -2
  66. package/plugins/admin-panel/core/registry.js +96 -16
  67. package/plugins/admin-panel/index.js +96 -0
  68. package/plugins/admin-panel/lib/login-rate-limiter.js +119 -0
  69. package/plugins/admin-panel/modules/admin-users.js +16 -1
  70. package/plugins/admin-panel/modules/bulk-actions.js +13 -5
  71. package/plugins/admin-panel/modules/custom-pages.js +492 -3
  72. package/plugins/admin-panel/modules/dashboard.js +283 -0
  73. package/plugins/admin-panel/modules/menu.js +56 -18
  74. package/plugins/admin-panel/modules/user-management.js +24 -6
  75. package/plugins/audit-log/admin-component.js +11 -0
  76. package/plugins/audit-log/api-handlers.js +34 -0
  77. package/plugins/audit-log/index.js +129 -3
  78. package/plugins/audit-log/purge.js +60 -10
  79. package/plugins/basic-auth/index.js +3 -1
  80. package/plugins/content/client/inline-edit.js +12 -12
  81. package/plugins/cors.js +6 -7
  82. package/plugins/dashboard/app.js +152 -27
  83. package/plugins/dashboard/index.js +143 -39
  84. package/plugins/dashboard/styles.js +78 -0
  85. package/plugins/data-exchange/export-xlsx.js +9 -2
  86. package/plugins/data-exchange/import.js +9 -2
  87. package/plugins/data-exchange/index.js +6 -3
  88. package/plugins/email/index.js +132 -2
  89. package/plugins/email/purge.js +60 -10
  90. package/plugins/file-manager/core.js +1 -1
  91. package/plugins/file-manager/security.js +33 -23
  92. package/plugins/index.js +49 -87
  93. package/plugins/mcp/transports/sse.js +4 -2
  94. package/plugins/orm-cache-admin/index.js +4 -2
  95. package/plugins/queue/index.js +11 -0
  96. package/plugins/schema-explorer/admin-component.js +714 -0
  97. package/plugins/schema-explorer/relations-graph.js +205 -0
  98. package/plugins/schema-explorer.js +248 -69
  99. package/plugins/site-analytics/admin-component.js +30 -8
  100. package/plugins/site-analytics/api-handlers.js +42 -0
  101. package/plugins/site-analytics/index.js +147 -2
  102. package/plugins/site-analytics/purge.js +168 -0
  103. package/plugins/swagger.js +261 -13
  104. package/plugins/upload/index.js +26 -0
  105. package/plugins/upload/orphan-pruner.js +292 -0
  106. package/src/api/api-loader.js +3 -1
  107. package/src/bundle/index.js +517 -0
  108. package/src/bundle/manifest.js +165 -0
  109. package/src/dev/banner.js +296 -0
  110. package/src/dev/colors.js +70 -0
  111. package/src/dev/index.js +62 -0
  112. package/src/dev/profiler.js +354 -0
  113. package/src/dev/typegen.js +497 -0
  114. package/src/dev-reload/index.js +611 -0
  115. package/src/file-router.js +213 -63
  116. package/src/helpers.js +958 -98
  117. package/src/njk-frontmatter.js +12 -0
  118. package/src/pages/page-loader.js +61 -11
  119. package/src/server.js +629 -63
  120. package/src/services/discovery.js +6 -1
  121. package/src/services/index.js +6 -13
package/index.d.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  import type { Application, NextFunction, Request, RequestHandler, Response } from 'express';
6
6
  import type { Knex } from 'knex';
7
7
  import type { ZodObject, ZodTypeAny } from 'zod';
8
+ import type { EventEmitter } from 'events';
8
9
 
9
10
  /** Registered as createApp middlewares[name]: plain handler or (options) => handler (for middleware: ['name', options]). */
10
11
  export type WebspressoRegisteredMiddleware =
@@ -45,6 +46,7 @@ export interface CreateAppOptions {
45
46
  enabled?: boolean;
46
47
  stylesheets?: boolean;
47
48
  scripts?: boolean;
49
+ auto?: boolean;
48
50
  };
49
51
  /** Trust proxy configuration (default: 1) */
50
52
  trustProxy?: boolean | number | string;
@@ -66,6 +68,17 @@ export interface CreateAppOptions {
66
68
  compression?: boolean | CompressionOptions;
67
69
  /** Shutdown and lifecycle configuration */
68
70
  shutdown?: ShutdownOptions;
71
+ /** Development tooling and DX options */
72
+ dev?: {
73
+ banner?: boolean;
74
+ routeTable?: boolean;
75
+ profiler?: boolean;
76
+ nPlusOne?: boolean;
77
+ nPlusOneThreshold?: number;
78
+ slowQueryMs?: number;
79
+ verbose?: boolean;
80
+ colors?: boolean;
81
+ };
69
82
  server?: {
70
83
  port?: number;
71
84
  shutdown?: ShutdownOptions;
@@ -83,10 +96,84 @@ export interface CreateAppOptions {
83
96
  services?: {
84
97
  dir?: string;
85
98
  };
99
+ /** Development SSE live/hot reload configuration */
100
+ devReload?: boolean | DevReloadOptions;
101
+ /** Client-side JS incremental bundler (esbuild context() API) */
102
+ bundle?: boolean | DevBundlerOptions;
86
103
  setupRoutes?: (app: Application, ctx: SetupRoutesContext) => void;
87
104
  [key: string]: unknown;
88
105
  }
89
106
 
107
+ export interface DevReloadOptions {
108
+ enabled?: boolean;
109
+ watch?: string[];
110
+ watchCss?: boolean;
111
+ watchTemplates?: boolean;
112
+ watchPublicAll?: boolean;
113
+ templateExtensions?: string[];
114
+ pageExtensions?: string[];
115
+ autoInject?: boolean;
116
+ debounce?: number;
117
+ }
118
+
119
+ export interface DevReloadHub {
120
+ broadcast(type: 'css' | 'full' | 'error' | 'clear-error' | string, payload?: Record<string, unknown>): void;
121
+ close(): void;
122
+ getClientsCount(): number;
123
+ getWatchersCount(): number;
124
+ getWatchedPaths(): string[];
125
+ attachNunjucks(env: unknown): void;
126
+ setOnTemplateChange(fn: (file?: string) => void): void;
127
+ invalidateTemplateCache(file?: string): void;
128
+ triggerTemplateReload(filename?: string, reason?: string): void;
129
+ triggerCssReload(filename?: string): void;
130
+ handleSse(req: any, res: any): void;
131
+ handleScript(req: any, res: any): void;
132
+ autoInjectMiddleware(req: any, res: any, next: () => void): void;
133
+ }
134
+
135
+ export interface DevBundlerOptions {
136
+ enabled?: boolean;
137
+ entryPoints?: string[] | Record<string, string>;
138
+ outdir?: string;
139
+ bundle?: boolean;
140
+ sourcemap?: boolean;
141
+ minify?: boolean;
142
+ target?: string | string[];
143
+ format?: string;
144
+ metafile?: boolean;
145
+ plugins?: any[];
146
+ esbuild?: any;
147
+ watch?: boolean;
148
+ esbuildOptions?: Record<string, unknown>;
149
+ [key: string]: unknown;
150
+ }
151
+
152
+ export interface DevBundler {
153
+ isAvailable(): boolean;
154
+ isWatching(): boolean;
155
+ isDisposed(): boolean;
156
+ getEntryPoints(): string[] | Record<string, string>;
157
+ getOutdir(): string;
158
+ startWatch(): Promise<any>;
159
+ rebuild(): Promise<any>;
160
+ dispose(): Promise<void>;
161
+ getContext(): any;
162
+ }
163
+
164
+ export interface BuildBundleResult {
165
+ result: unknown;
166
+ manifest: Record<string, unknown>;
167
+ manifestPath: string;
168
+ }
169
+
170
+ export function createDevBundler(options?: DevBundlerOptions, devReloadHub?: unknown): DevBundler;
171
+ export function buildBundle(options?: DevBundlerOptions): Promise<BuildBundleResult>;
172
+ export function generateManifest(metafile: unknown, options?: unknown): Record<string, unknown>;
173
+ export function createManifestPlugin(options?: unknown): unknown;
174
+ export function findDefaultEntryPoints(cwd?: string): string[] | Record<string, string>;
175
+ export function findPageEntryPoints(cwd?: string): Record<string, string>;
176
+
90
177
  export interface ServiceContext {
91
178
  req?: Request;
92
179
  res?: Response;
@@ -314,6 +401,11 @@ export interface WebspressoApplication extends Application {
314
401
  enableShutdownHooks(): this;
315
402
  disableShutdownHooks(): this;
316
403
  setErrorHandler(handler: (error: unknown, req: Request, res: Response, next: NextFunction) => Promise<unknown> | unknown): this;
404
+ broadcastDevReload?(type: 'css' | 'full' | string, payload?: Record<string, unknown>): void;
405
+ devReloadHub?: DevReloadHub;
406
+ devBundler?: DevBundler;
407
+ printDevBanner?(opts?: Record<string, unknown>): void;
408
+ formatDevBanner?(opts?: Record<string, unknown>): { bannerText: string; plainText: string; summary: Record<string, unknown> };
317
409
  }
318
410
 
319
411
  export interface CreateAppResult {
@@ -326,6 +418,11 @@ export interface CreateAppResult {
326
418
 
327
419
  export function createApp(options?: CreateAppOptions): CreateAppResult;
328
420
 
421
+ export function formatDevBanner(context?: Record<string, unknown>, options?: Record<string, unknown>): { bannerText: string; plainText: string; summary: Record<string, unknown> };
422
+ export function printDevBanner(context?: Record<string, unknown>, options?: Record<string, unknown>): void;
423
+ export function formatRouteTable(routeTable: unknown, options?: Record<string, unknown>): string;
424
+ export function createDevProfilerMiddleware(options?: Record<string, unknown>): RequestHandler;
425
+
329
426
  // --- Exceptions & Errors ---
330
427
 
331
428
  export interface ErrorOptions {
@@ -521,6 +618,19 @@ export const utils: Record<string, unknown>;
521
618
 
522
619
  export class AssetManager {
523
620
  constructor(options?: Record<string, unknown>);
621
+ resolve(assetPath: string): string;
622
+ asset(assetPath: string): string;
623
+ js(src: string, attributes?: Record<string, unknown>): string;
624
+ css(href: string, attributes?: Record<string, unknown>): string;
625
+ pageJs(name: string, attributes?: Record<string, unknown>): string;
626
+ pageJsUrl(name: string): string;
627
+ pageCss(name: string, attributes?: Record<string, unknown>): string;
628
+ pageCssUrl(name: string): string;
629
+ hasPageJs(name: string): boolean;
630
+ hasPageCss(name: string): boolean;
631
+ getRouteAssetCandidates(routeFile?: string, routePath?: string): string[];
632
+ pageAssets(nameOrOptions?: string | Record<string, unknown>, attributes?: Record<string, unknown>): string;
633
+ pageAssetsForRoute(routeFile?: string, routePath?: string, options?: Record<string, unknown>): string;
524
634
  [key: string]: unknown;
525
635
  }
526
636
 
@@ -603,10 +713,111 @@ export interface RelationDefinition {
603
713
  localKey?: string;
604
714
  }
605
715
 
716
+ export interface AdminPermissions {
717
+ create?: boolean;
718
+ edit?: boolean;
719
+ delete?: boolean;
720
+ }
721
+
722
+ export interface AdminSortConfig {
723
+ column: string;
724
+ direction?: 'asc' | 'desc';
725
+ }
726
+
727
+ export type AdminBadgeColor =
728
+ | 'green'
729
+ | 'red'
730
+ | 'blue'
731
+ | 'amber'
732
+ | 'yellow'
733
+ | 'purple'
734
+ | 'violet'
735
+ | 'indigo'
736
+ | 'gray'
737
+ | string;
738
+
739
+ export interface AdminColumnConfig {
740
+ label?: string;
741
+ badge?: Record<string, AdminBadgeColor>;
742
+ hidden?: boolean;
743
+ readonly?: boolean;
744
+ sortable?: boolean;
745
+ [key: string]: unknown;
746
+ }
747
+
748
+ export interface AdminActionConfig {
749
+ id: string;
750
+ label: string;
751
+ icon?: string;
752
+ color?: string;
753
+ models?: string | string[];
754
+ confirm?: boolean;
755
+ confirmMessage?: string;
756
+ showWhen?: Record<string, unknown>;
757
+ handler?: (record: any, modelName: string, ctx: any) => Promise<unknown> | unknown;
758
+ visible?: (record: any, modelName: string) => boolean;
759
+ }
760
+
761
+ export interface AdminBulkActionConfig {
762
+ id: string;
763
+ label: string;
764
+ icon?: string;
765
+ color?: string;
766
+ models?: string | string[];
767
+ confirm?: boolean;
768
+ confirmMessage?: string;
769
+ handler?: (records: any[], modelName: string, ctx: any) => Promise<unknown> | unknown;
770
+ }
771
+
772
+ export interface AdminHookContext<TData = any, TRecord = any> {
773
+ model: string;
774
+ data?: TData;
775
+ record?: TRecord;
776
+ id?: string | number;
777
+ req?: any;
778
+ user?: any;
779
+ db?: any;
780
+ stats?: any;
781
+ }
782
+
783
+ export interface AdminHooksConfig {
784
+ beforeCreate?: (ctx: AdminHookContext) => Promise<void> | void;
785
+ afterCreate?: (ctx: AdminHookContext) => Promise<void> | void;
786
+ beforeUpdate?: (ctx: AdminHookContext) => Promise<void> | void;
787
+ afterUpdate?: (ctx: AdminHookContext) => Promise<void> | void;
788
+ beforeDelete?: (ctx: AdminHookContext) => Promise<void> | void;
789
+ afterDelete?: (ctx: AdminHookContext) => Promise<void> | void;
790
+ beforeRestore?: (ctx: AdminHookContext) => Promise<void> | void;
791
+ afterRestore?: (ctx: AdminHookContext) => Promise<void> | void;
792
+ onRecordView?: (ctx: AdminHookContext) => Promise<void> | void;
793
+ onDashboardLoad?: (ctx: AdminHookContext) => Promise<void> | void;
794
+ [key: string]: ((ctx: AdminHookContext) => Promise<void> | void) | undefined;
795
+ }
796
+
797
+ export interface AdminRelationConfig {
798
+ displayField?: string;
799
+ searchFields?: string[];
800
+ limit?: number;
801
+ [key: string]: unknown;
802
+ }
803
+
606
804
  export interface AdminMetadata {
607
805
  enabled?: boolean;
608
806
  label?: string;
609
807
  icon?: string | null;
808
+ group?: string | null;
809
+ menuOrder?: number | null;
810
+ listColumns?: string[];
811
+ searchFields?: string[];
812
+ displayField?: string;
813
+ readOnly?: boolean;
814
+ permissions?: AdminPermissions;
815
+ defaultSort?: AdminSortConfig;
816
+ columns?: Record<string, AdminColumnConfig>;
817
+ actions?: AdminActionConfig[];
818
+ bulkActions?: AdminBulkActionConfig[];
819
+ hooks?: AdminHooksConfig;
820
+ relations?: Record<string, AdminRelationConfig>;
610
821
  customFields?: Record<string, unknown>;
611
822
  queries?: Record<string, (repo: Repository) => Promise<unknown>>;
612
823
  }
@@ -855,8 +1066,30 @@ export interface MigrationManager {
855
1066
  unlock(): Promise<void>;
856
1067
  }
857
1068
 
1069
+ export interface WebspressoModels {
1070
+ [key: string]: Record<string, any>;
1071
+ }
1072
+
1073
+ export interface WebspressoServices {
1074
+ [key: string]: { input: any; output: any };
1075
+ }
1076
+
1077
+ export interface WebspressoServiceCaller {
1078
+ <K extends keyof WebspressoServices>(
1079
+ name: K,
1080
+ input?: WebspressoServices[K]['input'],
1081
+ opts?: unknown
1082
+ ): Promise<WebspressoServices[K]['output']>;
1083
+ <TInput = unknown, TOutput = unknown>(
1084
+ name: string,
1085
+ input?: TInput,
1086
+ opts?: unknown
1087
+ ): Promise<TOutput>;
1088
+ }
1089
+
858
1090
  export interface TransactionContext {
859
1091
  trx: Knex.Transaction;
1092
+ getRepository<K extends keyof WebspressoModels>(modelName: K, scopeContext?: ScopeContext): Repository<WebspressoModels[K]>;
860
1093
  getRepository<T = Record<string, any>>(modelName: string, scopeContext?: ScopeContext): Repository<T>;
861
1094
  createRepository<T = Record<string, any>>(model: ModelDefinition<T>, scopeContext?: ScopeContext): Repository<T>;
862
1095
  }
@@ -868,6 +1101,7 @@ export interface DatabaseInstance {
868
1101
  hasModel(name: string): boolean;
869
1102
  getAllModels(): ModelDefinition<any>[];
870
1103
  registerModel<T = Record<string, any>>(model: ModelDefinition<T>): void;
1104
+ getRepository<K extends keyof WebspressoModels>(modelName: K, scopeContext?: ScopeContext): Repository<WebspressoModels[K]>;
871
1105
  getRepository<T = Record<string, any>>(modelName: string, scopeContext?: ScopeContext): Repository<T>;
872
1106
  createRepository<T = Record<string, any>>(model: ModelDefinition<T>, scopeContext?: ScopeContext): Repository<T>;
873
1107
  query<T = Record<string, any>>(modelName: string, scopeContext?: ScopeContext): QueryBuilder<T>;
@@ -974,21 +1208,295 @@ export function validateQueryComplexity(
974
1208
  includes: string[];
975
1209
  };
976
1210
 
977
- // --- Built-in plugins (subset re-exported from main index.js) ---
1211
+ export interface SchemaExplorerPluginOptions {
1212
+ /** Raw JSON API endpoint path (default: '/_schema') */
1213
+ path?: string;
1214
+ /** Force enable/disable raw API (default: auto based on NODE_ENV) */
1215
+ enabled?: boolean;
1216
+ /** Model names to exclude from schema exploration */
1217
+ exclude?: string[];
1218
+ /** Include column metadata (default: true) */
1219
+ includeColumns?: boolean;
1220
+ /** Include relation metadata (default: true) */
1221
+ includeRelations?: boolean;
1222
+ /** Include scope configuration (default: true) */
1223
+ includeScopes?: boolean;
1224
+ /** Custom authorization check for raw endpoint */
1225
+ authorize?: (req: any) => boolean;
1226
+ /** Register interactive page in Admin Panel when adminPanelPlugin is present (default: true) */
1227
+ admin?: boolean;
1228
+ /** Path within admin panel (default: '/schema') */
1229
+ adminPath?: string;
1230
+ /** Admin sidebar menu label (default: 'Schema Explorer') */
1231
+ menuLabel?: string;
1232
+ /** Admin sidebar icon (default: 'database') */
1233
+ menuIcon?: string;
1234
+ /** Admin sidebar menu order (default: 85) */
1235
+ menuOrder?: number;
1236
+ /** Admin page title (default: menuLabel or 'Schema Explorer') */
1237
+ title?: string;
1238
+ /** Admin page title alias */
1239
+ adminTitle?: string;
1240
+ /** Admin API prefix (default: '/schema') */
1241
+ apiPrefix?: string;
1242
+ }
978
1243
 
979
- export function schemaExplorerPlugin(options?: Record<string, unknown>): WebspressoPlugin;
1244
+ export interface DashboardPluginOptions {
1245
+ /** Base URL path for the developer dashboard (default: '/_webspresso') */
1246
+ path?: string;
1247
+ /** Custom dashboard title displayed in HTML <title> and header (default: 'Webspresso Developer Dashboard') */
1248
+ title?: string;
1249
+ /** Enable or disable the plugin (default: true) */
1250
+ enabled?: boolean;
1251
+ }
1252
+
1253
+ export function dashboardPlugin(options?: DashboardPluginOptions): WebspressoPlugin;
1254
+ export function devToolbarPlugin(options?: DashboardPluginOptions): WebspressoPlugin;
1255
+ export function devtoolsPlugin(options?: DashboardPluginOptions): WebspressoPlugin;
1256
+
1257
+ export function schemaExplorerPlugin(options?: SchemaExplorerPluginOptions): WebspressoPlugin;
1258
+
1259
+ export interface AdminPanelPluginOptions {
1260
+ /** Database instance (required) */
1261
+ db?: any;
1262
+ /** Base URL path for the admin panel (default: '/_admin') */
1263
+ path?: string;
1264
+ /** Enable or disable the plugin (default: true) */
1265
+ enabled?: boolean;
1266
+ /** Custom admin panel title displayed in HTML <title> and header (default: 'Admin Panel') */
1267
+ title?: string;
1268
+ /** Dynamic page title template where %s is page title and %b is base title (default: '%s | %b') */
1269
+ titleTemplate?: string;
1270
+ /** Session secret for admin auth cookie encryption */
1271
+ sessionSecret?: string;
1272
+ /** Auth manager instance for optional user management integration */
1273
+ auth?: any;
1274
+ /** User management configuration */
1275
+ userManagement?: {
1276
+ enabled?: boolean;
1277
+ model?: string;
1278
+ fields?: Record<string, string>;
1279
+ };
1280
+ /** Staff user management option (default: true) */
1281
+ adminUsers?: boolean | { enabled?: boolean };
1282
+ /** Custom registry configuration callback */
1283
+ configure?: (registry: any) => void;
1284
+ /** POST URL for file uploads */
1285
+ uploadUrl?: string;
1286
+ /** Sanitize rich-text HTML values (default: true) */
1287
+ richTextSanitize?: boolean;
1288
+ /** Auto-register data exchange export/import services (default: true) */
1289
+ dataExchange?: boolean;
1290
+ /** Admin login brute-force protection / rate limiting (default: enabled with 5 attempts per 15 mins) */
1291
+ loginRateLimit?: boolean | {
1292
+ enabled?: boolean;
1293
+ maxAttempts?: number;
1294
+ windowMs?: number;
1295
+ blockDurationMs?: number;
1296
+ };
1297
+ [key: string]: unknown;
1298
+ }
980
1299
 
981
- export function adminPanelPlugin(options?: Record<string, unknown>): WebspressoPlugin;
1300
+ export function adminPanelPlugin(options?: AdminPanelPluginOptions): WebspressoPlugin;
982
1301
 
983
1302
  export function dataExchangePlugin(options?: Record<string, unknown>): WebspressoPlugin;
984
1303
 
985
- export function siteAnalyticsPlugin(options?: Record<string, unknown>): WebspressoPlugin;
1304
+ export interface SiteAnalyticsRetentionConfig {
1305
+ enabled?: boolean;
1306
+ days?: number;
1307
+ errorDays?: number;
1308
+ intervalMs?: number;
1309
+ runOnStart?: boolean;
1310
+ }
986
1311
 
987
- export function auditLogPlugin(options?: Record<string, unknown>): WebspressoPlugin;
1312
+ export interface SiteAnalyticsPurgeResult {
1313
+ viewsDeleted: number;
1314
+ errorsDeleted: number;
1315
+ viewsCutoff: string;
1316
+ errorsCutoff: string;
1317
+ }
1318
+
1319
+ export interface SiteAnalyticsPurgeOptions {
1320
+ days?: number;
1321
+ errorDays?: number;
1322
+ olderThan?: Date;
1323
+ errorsOlderThan?: Date;
1324
+ tableName?: string;
1325
+ errorsTableName?: string;
1326
+ }
1327
+
1328
+ export interface SiteAnalyticsPluginOptions {
1329
+ db?: any;
1330
+ tableName?: string;
1331
+ errorsTableName?: string;
1332
+ excludePaths?: (string | RegExp)[];
1333
+ excludeBots?: boolean;
1334
+ customBots?: string[];
1335
+ anonymizeIp?: boolean;
1336
+ trustProxy?: boolean;
1337
+ collectErrors?: boolean;
1338
+ dashboard?: boolean;
1339
+ retentionDays?: number;
1340
+ errorRetentionDays?: number;
1341
+ cleanupIntervalMs?: number;
1342
+ autoCleanup?: boolean;
1343
+ cleanupOnStart?: boolean;
1344
+ retention?: boolean | SiteAnalyticsRetentionConfig;
1345
+ [key: string]: unknown;
1346
+ }
1347
+
1348
+ export function siteAnalyticsPlugin(options?: SiteAnalyticsPluginOptions): WebspressoPlugin & {
1349
+ api?: {
1350
+ purge(options?: SiteAnalyticsPurgeOptions): Promise<SiteAnalyticsPurgeResult>;
1351
+ purgeAnalyticsData(knex: any, options?: SiteAnalyticsPurgeOptions): Promise<SiteAnalyticsPurgeResult>;
1352
+ startCleanup(intervalMs?: number): void;
1353
+ stopCleanup(): void;
1354
+ getRetentionConfig(): SiteAnalyticsRetentionConfig;
1355
+ };
1356
+ dispose?(): void;
1357
+ shutdown?(): void;
1358
+ };
1359
+
1360
+ export interface AuditLogRetentionConfig {
1361
+ enabled?: boolean;
1362
+ autoCleanup?: boolean;
1363
+ days?: number;
1364
+ intervalMs?: number;
1365
+ runOnStart?: boolean;
1366
+ }
1367
+
1368
+ export interface AuditLogPluginOptions {
1369
+ db: any;
1370
+ title?: string;
1371
+ adminTitle?: string;
1372
+ pageTitle?: string;
1373
+ adminPath?: string;
1374
+ tableName?: string;
1375
+ includeDefaultPage?: boolean;
1376
+ apiPrefix?: string;
1377
+ retentionDays?: number;
1378
+ cleanupIntervalMs?: number;
1379
+ autoCleanup?: boolean;
1380
+ cleanupOnStart?: boolean;
1381
+ cleanupStartDelayMs?: number;
1382
+ retention?: boolean | AuditLogRetentionConfig;
1383
+ [key: string]: unknown;
1384
+ }
1385
+
1386
+ export function auditLogPlugin(options?: AuditLogPluginOptions): WebspressoPlugin & {
1387
+ api?: {
1388
+ purge(options?: { tableName?: string; days?: number; retentionDays?: number; olderThan?: Date | string | number }): Promise<number>;
1389
+ purgeAuditLogs(knex?: any, options?: { tableName?: string; days?: number; retentionDays?: number; olderThan?: Date | string | number }): Promise<number>;
1390
+ startCleanup(intervalMs?: number): void;
1391
+ stopCleanup(): void;
1392
+ getRetentionConfig(): AuditLogRetentionConfig;
1393
+ queryLogs(filters?: Record<string, unknown>): Promise<any[]>;
1394
+ getMigrationTemplate(tableName?: string): string;
1395
+ };
1396
+ dispose?(): void;
1397
+ shutdown?(): void;
1398
+ };
1399
+
1400
+ export interface EmailRetentionConfig {
1401
+ enabled?: boolean;
1402
+ autoCleanup?: boolean;
1403
+ days?: number;
1404
+ intervalMs?: number;
1405
+ runOnStart?: boolean;
1406
+ }
1407
+
1408
+ export interface EmailPluginOptions {
1409
+ db?: any;
1410
+ title?: string;
1411
+ adminTitle?: string;
1412
+ transport?: any;
1413
+ smtp?: any;
1414
+ defaults?: Record<string, unknown>;
1415
+ templatesDir?: string;
1416
+ templates?: Record<string, string>;
1417
+ tableName?: string;
1418
+ includeAdminPage?: boolean;
1419
+ adminPath?: string;
1420
+ apiPrefix?: string;
1421
+ auth?: any;
1422
+ authEmails?: any;
1423
+ emailTemplates?: any;
1424
+ manifest?: any;
1425
+ logToDb?: boolean;
1426
+ retentionDays?: number;
1427
+ cleanupIntervalMs?: number;
1428
+ autoCleanup?: boolean;
1429
+ cleanupOnStart?: boolean;
1430
+ cleanupStartDelayMs?: number;
1431
+ retention?: boolean | EmailRetentionConfig;
1432
+ [key: string]: unknown;
1433
+ }
1434
+
1435
+ export function emailPlugin(options?: EmailPluginOptions): WebspressoPlugin & {
1436
+ api?: {
1437
+ send(options: Record<string, unknown>): Promise<any>;
1438
+ sendTemplate(name: string, options: Record<string, unknown>): Promise<any>;
1439
+ preview(nameOrOpts: any, data?: Record<string, unknown>): Promise<any>;
1440
+ registerTemplate(id: string, source: string): void;
1441
+ listTemplates(): any[];
1442
+ verifyConnection(): Promise<any>;
1443
+ queryLogs(filters?: Record<string, unknown>): Promise<any>;
1444
+ purge(options?: { tableName?: string; days?: number; retentionDays?: number; olderThan?: Date | string | number }): Promise<number>;
1445
+ purgeLogs(options?: { tableName?: string; days?: number; retentionDays?: number; olderThan?: Date | string | number }): Promise<number>;
1446
+ startCleanup(intervalMs?: number): void;
1447
+ stopCleanup(): void;
1448
+ getRetentionConfig(): EmailRetentionConfig;
1449
+ getMigrationTemplate(tableName?: string): string;
1450
+ isTransportReady(): boolean;
1451
+ loadFromManifest(chunks: any): void;
1452
+ emailPluginOptionsFromManifest(manifest: any): any;
1453
+ };
1454
+ dispose?(): void;
1455
+ shutdown?(): void;
1456
+ };
988
1457
 
989
1458
  export function recaptchaPlugin(options?: Record<string, unknown>): WebspressoPlugin;
990
1459
 
991
- export function swaggerPlugin(options?: Record<string, unknown>): WebspressoPlugin;
1460
+ export interface SwaggerPluginOptions {
1461
+ /** Base path for docs (default: '/_swagger') */
1462
+ path?: string;
1463
+ /** UI playground engine: 'swagger' or 'scalar' (default: 'swagger') */
1464
+ ui?: 'swagger' | 'scalar';
1465
+ /** Enable plugin (defaults to true in development) */
1466
+ enabled?: boolean;
1467
+ /** Custom authorization guard */
1468
+ authorize?: (req: any) => boolean;
1469
+ /** Automatically merge ORM model schemas into components.schemas */
1470
+ includeOrmSchemas?: boolean;
1471
+ /** Exclude specific ORM models from documentation */
1472
+ ormExclude?: string[];
1473
+ /** OpenAPI info.title override */
1474
+ title?: string;
1475
+ /** OpenAPI info.version override */
1476
+ version?: string;
1477
+ /** OpenAPI info.description override */
1478
+ description?: string;
1479
+ /** Primary server URL in spec */
1480
+ serverUrl?: string;
1481
+ /** Theme for Scalar UI ('default', 'purple', 'solarized', etc.) */
1482
+ theme?: string;
1483
+ /** Dark mode default for Scalar UI */
1484
+ darkMode?: boolean;
1485
+ /** Auto-register in Admin Panel if admin-panel plugin is present (default: true) */
1486
+ admin?: boolean;
1487
+ /** Admin menu and page title (default: 'API Docs') */
1488
+ adminTitle?: string;
1489
+ /** Admin menu sort order (default: 92) */
1490
+ adminOrder?: number;
1491
+ /** Custom options passed to Scalar */
1492
+ scalarOptions?: Record<string, unknown>;
1493
+ /** Custom options passed to Swagger UI */
1494
+ swaggerOptions?: Record<string, unknown>;
1495
+ }
1496
+
1497
+ export function swaggerPlugin(options?: SwaggerPluginOptions): WebspressoPlugin;
1498
+ export function openapiPlugin(options?: SwaggerPluginOptions): WebspressoPlugin;
1499
+ export function apiDocsPlugin(options?: SwaggerPluginOptions): WebspressoPlugin;
992
1500
 
993
1501
  export function healthCheckPlugin(options?: Record<string, unknown>): WebspressoPlugin;
994
1502
 
@@ -1183,6 +1691,42 @@ export function createLocalFileProvider(options?: {
1183
1691
  publicBasePath?: string;
1184
1692
  }): UploadStorageProvider;
1185
1693
 
1694
+ export interface OrphanedUploadsResult {
1695
+ destDir: string;
1696
+ totalFiles: number;
1697
+ totalBytes: number;
1698
+ totalBytesFormatted: string;
1699
+ referencedCount: number;
1700
+ orphanedCount: number;
1701
+ orphanedBytes: number;
1702
+ orphanedBytesFormatted: string;
1703
+ orphanedFiles: Array<{
1704
+ relativePath: string;
1705
+ absolutePath: string;
1706
+ size: number;
1707
+ publicUrl: string;
1708
+ mtime: Date;
1709
+ }>;
1710
+ dryRun?: boolean;
1711
+ deletedCount?: number;
1712
+ deletedBytes?: number;
1713
+ deletedBytesFormatted?: string;
1714
+ deletedFiles?: string[];
1715
+ }
1716
+
1717
+ export function scanOrphanedUploads(options?: {
1718
+ db?: any;
1719
+ destDir?: string;
1720
+ publicBasePath?: string;
1721
+ }): Promise<OrphanedUploadsResult>;
1722
+
1723
+ export function pruneOrphanedUploads(options?: {
1724
+ db?: any;
1725
+ destDir?: string;
1726
+ publicBasePath?: string;
1727
+ dryRun?: boolean;
1728
+ }): Promise<OrphanedUploadsResult>;
1729
+
1186
1730
  // --- Realtime Layer Types ---
1187
1731
 
1188
1732
  export interface RealtimeSubscriptionCallbacks {
@@ -1508,6 +2052,13 @@ export class MemoryQueueAdapter implements QueueAdapter {
1508
2052
  getJob(jobId: string | number): Promise<Job | null>;
1509
2053
  getStats(): Promise<{ pending: number; running: number; completed: number; failed: number; total: number }>;
1510
2054
  clear(): Promise<void>;
2055
+ clean?(options?: {
2056
+ status?: string | string[];
2057
+ days?: number;
2058
+ retentionDays?: number;
2059
+ olderThan?: Date | string | number;
2060
+ limit?: number;
2061
+ }): Promise<number>;
1511
2062
  }
1512
2063
 
1513
2064
  export class DatabaseQueueAdapter implements QueueAdapter {
@@ -1521,6 +2072,13 @@ export class DatabaseQueueAdapter implements QueueAdapter {
1521
2072
  getJob(jobId: string | number): Promise<Job | null>;
1522
2073
  getStats(): Promise<{ pending: number; running: number; completed: number; failed: number; total: number }>;
1523
2074
  clear(): Promise<void>;
2075
+ clean(options?: {
2076
+ status?: string | string[];
2077
+ days?: number;
2078
+ retentionDays?: number;
2079
+ olderThan?: Date | string | number;
2080
+ limit?: number;
2081
+ }): Promise<number>;
1524
2082
  }
1525
2083
 
1526
2084
  export class RedisQueueAdapter implements QueueAdapter {
@@ -1533,6 +2091,13 @@ export class RedisQueueAdapter implements QueueAdapter {
1533
2091
  getJob(jobId: string | number): Promise<Job | null>;
1534
2092
  getStats(): Promise<{ pending: number; running: number; completed: number; failed: number; total: number }>;
1535
2093
  clear(): Promise<void>;
2094
+ clean(options?: {
2095
+ status?: string | string[];
2096
+ days?: number;
2097
+ retentionDays?: number;
2098
+ olderThan?: Date | string | number;
2099
+ limit?: number;
2100
+ }): Promise<number>;
1536
2101
  }
1537
2102
 
1538
2103
  export interface DispatchOptions {
@@ -1543,19 +2108,35 @@ export interface DispatchOptions {
1543
2108
  backoff?: number | { type?: 'exponential' | 'fixed'; delay?: number };
1544
2109
  }
1545
2110
 
1546
- export class QueueManager extends import('events').EventEmitter {
2111
+ export interface QueueRetentionConfig {
2112
+ enabled?: boolean;
2113
+ autoCleanup?: boolean;
2114
+ days?: number;
2115
+ statuses?: string[];
2116
+ intervalMs?: number;
2117
+ runOnStart?: boolean;
2118
+ }
2119
+
2120
+ export class QueueManager extends EventEmitter {
1547
2121
  adapter: QueueAdapter;
1548
2122
  concurrency: number;
1549
2123
  pollInterval: number;
1550
2124
  isRunning: boolean;
1551
2125
  isPaused: boolean;
1552
2126
  isDraining: boolean;
2127
+ retentionConfig: QueueRetentionConfig;
1553
2128
  constructor(options?: {
1554
2129
  adapter?: QueueAdapter;
1555
2130
  concurrency?: number;
1556
2131
  pollInterval?: number;
1557
2132
  jobsDir?: string | null;
1558
2133
  autoStart?: boolean;
2134
+ retention?: boolean | QueueRetentionConfig;
2135
+ retentionDays?: number;
2136
+ retentionStatuses?: string[];
2137
+ cleanupIntervalMs?: number;
2138
+ autoCleanup?: boolean;
2139
+ cleanupOnStart?: boolean;
1559
2140
  });
1560
2141
  define(name: string, handler: (job: Job, ctx: { queue: QueueManager }) => Promise<any> | any, options?: DispatchOptions): this;
1561
2142
  has(name: string): boolean;
@@ -1565,6 +2146,17 @@ export class QueueManager extends import('events').EventEmitter {
1565
2146
  resume(): this;
1566
2147
  drain(timeoutMs?: number): Promise<void>;
1567
2148
  stop(): Promise<void>;
2149
+ clean(options?: {
2150
+ status?: string | string[];
2151
+ days?: number;
2152
+ retentionDays?: number;
2153
+ olderThan?: Date | string | number;
2154
+ limit?: number;
2155
+ }): Promise<number>;
2156
+ runCleanup(): Promise<number>;
2157
+ startCleanup(customIntervalMs?: number): void;
2158
+ stopCleanup(): void;
2159
+ getRetentionConfig(): QueueRetentionConfig;
1568
2160
  getStats(): Promise<Record<string, unknown>>;
1569
2161
  getJob(jobId: string | number): Promise<Job | null>;
1570
2162
  loadJobsFromDirectory(dirPath: string): void;
@@ -1576,6 +2168,12 @@ export function createQueueManager(options?: {
1576
2168
  pollInterval?: number;
1577
2169
  jobsDir?: string | null;
1578
2170
  autoStart?: boolean;
2171
+ retention?: boolean | QueueRetentionConfig;
2172
+ retentionDays?: number;
2173
+ retentionStatuses?: string[];
2174
+ cleanupIntervalMs?: number;
2175
+ autoCleanup?: boolean;
2176
+ cleanupOnStart?: boolean;
1579
2177
  }): QueueManager;
1580
2178
 
1581
2179
  export function queuePlugin(options?: {
@@ -1588,7 +2186,24 @@ export function queuePlugin(options?: {
1588
2186
  pollInterval?: number;
1589
2187
  jobsDir?: string;
1590
2188
  autoStart?: boolean;
1591
- }): PluginDefinition;
2189
+ retention?: boolean | QueueRetentionConfig;
2190
+ retentionDays?: number;
2191
+ retentionStatuses?: string[];
2192
+ cleanupIntervalMs?: number;
2193
+ autoCleanup?: boolean;
2194
+ cleanupOnStart?: boolean;
2195
+ }): WebspressoPlugin & {
2196
+ api?: {
2197
+ getQueueManager(): QueueManager | null;
2198
+ clean(options?: {
2199
+ status?: string | string[];
2200
+ days?: number;
2201
+ retentionDays?: number;
2202
+ olderThan?: Date | string | number;
2203
+ limit?: number;
2204
+ }): Promise<number>;
2205
+ };
2206
+ };
1592
2207
 
1593
2208
  // --- SSR Streaming & Chunked Transfer ---
1594
2209
 
@@ -1863,7 +2478,7 @@ export interface PageContext<TParams = Record<string, string>, TQuery = Record<s
1863
2478
  res: import('express').Response;
1864
2479
  params: TParams;
1865
2480
  query: TQuery;
1866
- service: <TInput = unknown, TOutput = unknown>(name: string, input?: TInput, opts?: unknown) => Promise<TOutput>;
2481
+ service: WebspressoServiceCaller;
1867
2482
  module: string | null;
1868
2483
  config: Record<string, unknown>;
1869
2484
  logger: Console;
@@ -1897,7 +2512,7 @@ export interface ApiContext<TBody = any, TParams = Record<string, string>, TQuer
1897
2512
  query: TQuery;
1898
2513
  body: TBody;
1899
2514
  input: { body?: TBody; params?: TParams; query?: TQuery; headers?: Record<string, string> };
1900
- service: <TInput = unknown, TOutput = unknown>(name: string, input?: TInput, opts?: unknown) => Promise<TOutput>;
2515
+ service: WebspressoServiceCaller;
1901
2516
  module: string | null;
1902
2517
  config: Record<string, unknown>;
1903
2518
  logger: Console;
@@ -1980,6 +2595,41 @@ export function compileRouteTable(
1980
2595
  options?: { explicitRoutes?: Array<{ method?: string; path: string }>; isDev?: boolean }
1981
2596
  ): RouteTable;
1982
2597
 
2598
+ export interface GenerateTypesOptions {
2599
+ /** Root directory of the project (default: process.cwd()) */
2600
+ rootDir?: string;
2601
+ /** Alias for rootDir */
2602
+ cwd?: string;
2603
+ /** Models directory relative to root (default: 'models') */
2604
+ modelsDir?: string;
2605
+ /** Services directory relative to root (default: 'services') */
2606
+ servicesDir?: string;
2607
+ /** Pages directory relative to root (default: 'pages') */
2608
+ pagesDir?: string;
2609
+ /** Output directory (default: '.webspresso') */
2610
+ outDir?: string;
2611
+ /** Custom output file path (default: '.webspresso/types.d.ts') */
2612
+ outFile?: string;
2613
+ /** Create companion webspresso-env.d.ts at project root (default: true) */
2614
+ createEnvFile?: boolean;
2615
+ /** Write declaration files to disk (default: true) */
2616
+ write?: boolean;
2617
+ /** Suppress console output (default: false) */
2618
+ silent?: boolean;
2619
+ }
2620
+
2621
+ export interface GenerateTypesResult {
2622
+ code: string;
2623
+ modelsCount: number;
2624
+ servicesCount: number;
2625
+ routesCount: number;
2626
+ outFile: string;
2627
+ envFile: string | null;
2628
+ }
2629
+
2630
+ export function generateTypes(options?: GenerateTypesOptions): GenerateTypesResult;
2631
+
2632
+
1983
2633
 
1984
2634
 
1985
2635