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.
Files changed (125) hide show
  1. package/README.md +98 -2509
  2. package/bin/commands/admin-password.js +8 -2
  3. package/bin/commands/api.js +7 -1
  4. package/bin/commands/favicon-generate.js +6 -4
  5. package/bin/commands/mcp.js +162 -0
  6. package/bin/commands/new.js +7 -1
  7. package/bin/commands/page.js +7 -1
  8. package/bin/commands/seed.js +7 -1
  9. package/bin/commands/service.js +199 -0
  10. package/bin/commands/skill.js +7 -1
  11. package/bin/webspresso.js +4 -0
  12. package/core/auth/middleware.js +9 -0
  13. package/core/compileSchema.js +1 -5
  14. package/core/compression/compressible.js +63 -0
  15. package/core/compression/compression-stream.js +59 -0
  16. package/core/compression/index.js +25 -0
  17. package/core/compression/middleware.js +310 -0
  18. package/core/compression/negotiate.js +165 -0
  19. package/core/errors/base.js +31 -0
  20. package/core/errors/domain.js +193 -0
  21. package/core/errors/http.js +133 -0
  22. package/core/errors/index.js +77 -0
  23. package/core/errors/middleware.js +162 -0
  24. package/core/errors/normalize.js +171 -0
  25. package/core/kernel/app.js +2 -1
  26. package/core/kernel/index.js +5 -1
  27. package/core/orm/cache/fingerprint.js +23 -5
  28. package/core/orm/complexity.js +120 -0
  29. package/core/orm/eager-loader.js +54 -9
  30. package/core/orm/events.js +64 -14
  31. package/core/orm/index.js +83 -12
  32. package/core/orm/model.js +13 -2
  33. package/core/orm/query-builder.js +30 -4
  34. package/core/orm/repository.js +57 -25
  35. package/core/orm/transaction.js +39 -5
  36. package/core/orm/utils.js +6 -3
  37. package/core/queue/adapters/database.js +317 -0
  38. package/core/queue/adapters/memory.js +176 -0
  39. package/core/queue/adapters/redis.js +357 -0
  40. package/core/queue/index.js +21 -0
  41. package/core/queue/job.js +103 -0
  42. package/core/queue/manager.js +413 -0
  43. package/core/realtime/adapters/redis.js +431 -0
  44. package/core/realtime/adapters/socket-io.js +203 -0
  45. package/core/realtime/adapters/sse.js +210 -0
  46. package/core/realtime/adapters/websocket.js +337 -0
  47. package/core/realtime/auth-manager.js +140 -0
  48. package/core/realtime/core.js +359 -0
  49. package/core/realtime/emitter.js +98 -0
  50. package/core/realtime/errors.js +71 -0
  51. package/core/realtime/index.js +79 -0
  52. package/core/realtime/reconnect-manager.js +118 -0
  53. package/core/realtime/registry.js +243 -0
  54. package/core/shutdown/http-adapter.js +134 -0
  55. package/core/shutdown/index.js +12 -0
  56. package/core/shutdown/shutdown-manager.js +312 -0
  57. package/core/ssr/index.js +13 -0
  58. package/core/ssr/stream.js +174 -0
  59. package/core/validation/index.js +70 -0
  60. package/index.d.ts +924 -53
  61. package/index.js +110 -1
  62. package/package.json +30 -2
  63. package/plugins/admin-panel/api.js +37 -4
  64. package/plugins/admin-panel/app.js +57 -17
  65. package/plugins/admin-panel/client/parts/01-state-api-breadcrumb.js +10 -2
  66. package/plugins/admin-panel/client/parts/04-field-renderers.js +102 -58
  67. package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +51 -27
  68. package/plugins/admin-panel/client/parts/07-model-list.js +9 -4
  69. package/plugins/admin-panel/client/parts/08-record-list.js +37 -26
  70. package/plugins/admin-panel/client/parts/09-record-form.js +168 -19
  71. package/plugins/admin-panel/core/admin-module.js +2 -2
  72. package/plugins/admin-panel/core/api-extensions.js +149 -0
  73. package/plugins/admin-panel/field-renderers/rich-text.js +3 -2
  74. package/plugins/admin-panel/index.js +155 -23
  75. package/plugins/admin-panel/modules/bulk-actions.js +9 -2
  76. package/plugins/admin-panel/modules/custom-pages.js +144 -45
  77. package/plugins/admin-panel/modules/dashboard.js +8 -1
  78. package/plugins/admin-panel/modules/menu.js +85 -0
  79. package/plugins/admin-panel/modules/user-management.js +5 -2
  80. package/plugins/admin-panel/vendor/mithril.min.js +1 -0
  81. package/plugins/admin-panel/vendor/quill.min.js +7 -0
  82. package/plugins/admin-panel/vendor/quill.snow.min.css +945 -0
  83. package/plugins/admin-panel/vendor/tailwind.min.js +65 -0
  84. package/plugins/basic-auth/index.js +293 -0
  85. package/plugins/data-exchange/export-xlsx.js +28 -2
  86. package/plugins/data-exchange/import.js +55 -16
  87. package/plugins/data-exchange/index.js +19 -0
  88. package/plugins/data-exchange/parse-table.js +14 -5
  89. package/plugins/data-exchange/record-selection.js +2 -0
  90. package/plugins/email/index.js +14 -1
  91. package/plugins/index.js +33 -0
  92. package/plugins/mcp/auth.js +104 -0
  93. package/plugins/mcp/discovery.js +88 -0
  94. package/plugins/mcp/index.js +208 -0
  95. package/plugins/mcp/prompts.js +149 -0
  96. package/plugins/mcp/providers/orm.js +325 -0
  97. package/plugins/mcp/providers/services.js +190 -0
  98. package/plugins/mcp/providers/system.js +87 -0
  99. package/plugins/mcp/server.js +472 -0
  100. package/plugins/mcp/transports/sse.js +139 -0
  101. package/plugins/mcp/transports/stdio.js +108 -0
  102. package/plugins/queue/index.js +108 -0
  103. package/plugins/realtime/index.js +150 -0
  104. package/plugins/recaptcha/index.js +11 -0
  105. package/plugins/redirect/index.js +9 -1
  106. package/plugins/rest-resources/index.js +280 -11
  107. package/plugins/upload/index.js +16 -0
  108. package/src/app-context.js +46 -6
  109. package/src/file-router.js +366 -130
  110. package/src/helpers.js +20 -2
  111. package/src/njk-frontmatter.js +24 -14
  112. package/src/plugin-manager.js +124 -4
  113. package/src/server.js +332 -54
  114. package/src/services/builtins/auth.js +273 -0
  115. package/src/services/builtins/exchange.js +229 -0
  116. package/src/services/builtins/mail.js +134 -0
  117. package/src/services/builtins/media.js +196 -0
  118. package/src/services/builtins/system.js +221 -0
  119. package/src/services/discovery.js +141 -0
  120. package/src/services/executor.js +292 -0
  121. package/src/services/index.js +70 -0
  122. package/src/services/memoize.js +256 -0
  123. package/src/services/registry.js +290 -0
  124. package/src/services/validator.js +230 -0
  125. package/templates/skills/webspresso-usage/REFERENCE-framework.md +6 -4
package/index.d.ts CHANGED
@@ -34,7 +34,21 @@ export interface CreateAppOptions {
34
34
  version?: string;
35
35
  manifestPath?: string;
36
36
  prefix?: string;
37
- }; /**
37
+ publicDir?: string;
38
+ };
39
+ /**
40
+ * If truthy, route load() return values for stylesheets and scripts are promoted to pageHead
41
+ */
42
+ pageAssets?:
43
+ | boolean
44
+ | {
45
+ enabled?: boolean;
46
+ stylesheets?: boolean;
47
+ scripts?: boolean;
48
+ };
49
+ /** Trust proxy configuration (default: 1) */
50
+ trustProxy?: boolean | number | string;
51
+ /**
38
52
  * Custom 404 / 500 / 503 handlers. File-based route errors are forwarded with `next(err)` and hit `serverError` / `timeout`.
39
53
  * When `serverError` / `timeout` is a template path, it is not used for paths under `/api` (default JSON instead).
40
54
  */
@@ -48,15 +62,235 @@ export interface CreateAppOptions {
48
62
  timeout?: string | false;
49
63
  auth?: unknown;
50
64
  db?: DatabaseInstance | null;
65
+ /** HTTP response compression options */
66
+ compression?: boolean | CompressionOptions;
67
+ /** Shutdown and lifecycle configuration */
68
+ shutdown?: ShutdownOptions;
69
+ server?: {
70
+ port?: number;
71
+ shutdown?: ShutdownOptions;
72
+ compression?: boolean | CompressionOptions;
73
+ trustProxy?: boolean | number | string;
74
+ [key: string]: unknown;
75
+ };
51
76
  /** Opt-in Alpine / swup assets under `/__webspresso/client-runtime/*`. Env: WEBSPRESSO_ALPINE, WEBSPRESSO_SWUP. */
52
77
  clientRuntime?: {
53
78
  alpine?: boolean | Record<string, unknown>;
54
79
  swup?: boolean | Record<string, unknown>;
55
80
  };
81
+ /** Path to services directory (auto-discovered) */
82
+ servicesDir?: string;
83
+ services?: {
84
+ dir?: string;
85
+ };
56
86
  setupRoutes?: (app: Application, ctx: SetupRoutesContext) => void;
57
87
  [key: string]: unknown;
58
88
  }
59
89
 
90
+ export interface ServiceContext {
91
+ req?: Request;
92
+ res?: Response;
93
+ db?: DatabaseInstance | null;
94
+ fsy?: Record<string, unknown>;
95
+ locale?: string;
96
+ t?: unknown;
97
+ service?: {
98
+ <T = unknown>(name: string, input?: unknown, options?: unknown): Promise<T>;
99
+ invalidate?: (name: string, input?: unknown, ctx?: unknown) => boolean;
100
+ clearCache?: (name?: string) => boolean;
101
+ };
102
+ [key: string]: unknown;
103
+ }
104
+
105
+ export type ServiceHandler<TInput = unknown, TOutput = unknown> = (
106
+ input: TInput,
107
+ ctx: ServiceContext
108
+ ) => Promise<TOutput> | TOutput;
109
+
110
+ export interface ServiceDefinition<TInput = unknown, TOutput = unknown> {
111
+ schema?: import('zod').ZodType<any> | Record<string, unknown> | ((input: unknown) => unknown) | null;
112
+ handler: ServiceHandler<TInput, TOutput>;
113
+ metadata?: Record<string, unknown>;
114
+ auth?: boolean | string | string[] | ((user: unknown, ctx: ServiceContext) => boolean | Promise<boolean>);
115
+ cache?:
116
+ | boolean
117
+ | string
118
+ | number
119
+ | {
120
+ ttl?: string | number;
121
+ key?: string | ((input: unknown, ctx: ServiceContext) => string);
122
+ };
123
+ timeout?: number | string;
124
+ transaction?: boolean;
125
+ [key: string]: unknown;
126
+ }
127
+
128
+ export class ServiceRegistry {
129
+ constructor(options?: { servicesDir?: string; isDev?: boolean; logger?: unknown });
130
+ readonly services: Map<string, ServiceDefinition>;
131
+ load(): this;
132
+ register(name: string, definition: ServiceDefinition | ServiceHandler): this;
133
+ get(name: string): ServiceDefinition | undefined;
134
+ has(name: string): boolean;
135
+ list(): string[];
136
+ invalidate(name: string, input?: unknown, ctx?: unknown): boolean;
137
+ clearCache(name?: string): boolean;
138
+ call<T = unknown>(name: string, input?: unknown, ctx?: unknown, options?: unknown): Promise<T>;
139
+ reload(): this;
140
+ }
141
+
142
+ export function createServiceRegistry(options?: {
143
+ servicesDir?: string;
144
+ isDev?: boolean;
145
+ logger?: unknown;
146
+ autoLoad?: boolean;
147
+ }): ServiceRegistry;
148
+
149
+ export type InferServiceInput<T> = T extends ServiceDefinition<infer TInput, any>
150
+ ? TInput
151
+ : T extends { schema: import('zod').ZodType<infer TInput> }
152
+ ? TInput
153
+ : T extends (input: infer TInput, ...args: any[]) => any
154
+ ? TInput
155
+ : unknown;
156
+
157
+ export type InferServiceOutput<T> = T extends ServiceDefinition<any, infer TOutput>
158
+ ? TOutput
159
+ : T extends (...args: any[]) => Promise<infer TOutput>
160
+ ? TOutput
161
+ : T extends (...args: any[]) => infer TOutput
162
+ ? TOutput
163
+ : unknown;
164
+
165
+ export function defineService<TInput = unknown, TOutput = unknown>(
166
+ definition: ServiceDefinition<TInput, TOutput> | ServiceHandler<TInput, TOutput>
167
+ ): ServiceDefinition<TInput, TOutput>;
168
+
169
+ export const service: typeof defineService;
170
+
171
+ export interface MemoizeOptions {
172
+ ttl?: string | number | boolean;
173
+ maxSize?: number;
174
+ key?: (input: unknown, ctx?: unknown) => string;
175
+ }
176
+
177
+ export interface MemoizedFunction<TArgs extends unknown[], TReturn> {
178
+ (...args: TArgs): Promise<TReturn>;
179
+ invalidate(...args: TArgs): boolean;
180
+ delete(...args: TArgs): boolean;
181
+ clear(): void;
182
+ has(...args: TArgs): boolean;
183
+ readonly size: number;
184
+ }
185
+
186
+ export function memoize<TArgs extends unknown[], TReturn>(
187
+ fn: (...args: TArgs) => Promise<TReturn> | TReturn,
188
+ options?: MemoizeOptions
189
+ ): MemoizedFunction<TArgs, TReturn>;
190
+
191
+ export function parseTtlMs(ttl: string | number | boolean): number;
192
+
193
+ export function stableCacheKey(input: unknown): string;
194
+
195
+ export function sanitizeInput<T = unknown>(input: T): T;
196
+
197
+ export function maskSensitiveData<T = unknown>(data: T, customRedactKeys?: string[]): T;
198
+
199
+ export function discoverServices(
200
+ servicesDir: string
201
+ ): Array<{ name: string; aliases: string[]; filePath: string; relativePath: string }>;
202
+
203
+ export function filePathToServiceName(relativePath: string): { name: string; aliases: string[] };
204
+
205
+ export function toCamelCase(str: string): string;
206
+
207
+ export function createAuthServices(options?: {
208
+ userModel?: string;
209
+ fields?: Record<string, string>;
210
+ }): Record<string, ServiceDefinition>;
211
+
212
+ export function createMailServices(options?: {
213
+ emailService: unknown;
214
+ registry?: unknown;
215
+ db?: unknown;
216
+ tableName?: string;
217
+ }): Record<string, ServiceDefinition>;
218
+
219
+ export function createMediaServices(options?: {
220
+ provider?: unknown;
221
+ destDir?: string;
222
+ publicBasePath?: string;
223
+ }): Record<string, ServiceDefinition>;
224
+
225
+ export function createSystemServices(options?: {
226
+ db?: unknown;
227
+ serviceRegistry?: unknown;
228
+ pluginManager?: unknown;
229
+ }): Record<string, ServiceDefinition>;
230
+
231
+ export function createExchangeServices(options?: {
232
+ db?: unknown;
233
+ }): Record<string, ServiceDefinition>;
234
+
235
+ export function validateServiceInput(schema: unknown, input: unknown, serviceName: string): unknown;
236
+
237
+ export function executeService(
238
+ registry: ServiceRegistry,
239
+ name: string,
240
+ input?: unknown,
241
+ ctx?: unknown,
242
+ options?: unknown
243
+ ): Promise<unknown>;
244
+
245
+ export interface CompressionOptions {
246
+ enabled?: boolean;
247
+ threshold?: number;
248
+ level?: number;
249
+ encodings?: string[];
250
+ filter?: (req: Request, res: Response) => boolean;
251
+ brotli?: Record<string, unknown>;
252
+ gzip?: Record<string, unknown>;
253
+ deflate?: Record<string, unknown>;
254
+ }
255
+
256
+ export interface ShutdownOptions {
257
+ enabled?: boolean;
258
+ mode?: 'graceful' | 'force';
259
+ timeout?: number;
260
+ signals?: string[];
261
+ logger?: unknown;
262
+ exitOnSignal?: boolean;
263
+ }
264
+
265
+ export interface ServerAdapter {
266
+ close(): Promise<void>;
267
+ forceClose?(): void;
268
+ closeIdleConnections?(): void;
269
+ }
270
+
271
+ export class NodeHttpAdapter implements ServerAdapter {
272
+ constructor(server: unknown);
273
+ close(): Promise<void>;
274
+ forceClose(): void;
275
+ closeIdleConnections(): void;
276
+ destroy(): void;
277
+ readonly isListening: boolean;
278
+ }
279
+
280
+ export class ShutdownManager {
281
+ constructor(options?: ShutdownOptions);
282
+ readonly isShuttingDown: boolean;
283
+ readonly isClosed: boolean;
284
+ mode: 'graceful' | 'force';
285
+ timeout: number;
286
+ onShutdown(fn: () => Promise<void> | void): this;
287
+ registerDisposer(nameOrFn: string | (() => Promise<void> | void), maybeFn?: () => Promise<void> | void): this;
288
+ registerAdapter(adapter: ServerAdapter): this;
289
+ enableShutdownHooks(): this;
290
+ disableShutdownHooks(): this;
291
+ close(reason?: string): Promise<void>;
292
+ }
293
+
60
294
  export interface SetupRoutesContext {
61
295
  nunjucksEnv: unknown;
62
296
  authMiddleware?: RequestHandler;
@@ -65,15 +299,97 @@ export interface SetupRoutesContext {
65
299
  clientRuntime: { alpine: boolean; swup: boolean };
66
300
  }
67
301
 
302
+ export interface WebspressoApplication extends Application {
303
+ server?: unknown;
304
+ readonly isShuttingDown: boolean;
305
+ shutdownManager: ShutdownManager;
306
+ serviceRegistry: ServiceRegistry;
307
+ onShutdown(fn: () => Promise<void> | void): this;
308
+ close(reason?: string): Promise<void>;
309
+ enableShutdownHooks(): this;
310
+ disableShutdownHooks(): this;
311
+ setErrorHandler(handler: (error: unknown, req: Request, res: Response, next: NextFunction) => Promise<unknown> | unknown): this;
312
+ }
313
+
68
314
  export interface CreateAppResult {
69
- app: Application;
315
+ app: WebspressoApplication;
70
316
  nunjucksEnv: unknown;
71
317
  pluginManager: PluginManager;
72
318
  authMiddleware?: RequestHandler;
319
+ shutdownManager: ShutdownManager;
73
320
  }
74
321
 
75
322
  export function createApp(options?: CreateAppOptions): CreateAppResult;
76
323
 
324
+ // --- Exceptions & Errors ---
325
+
326
+ export interface ErrorOptions {
327
+ code?: string;
328
+ details?: unknown;
329
+ cause?: unknown;
330
+ status?: number;
331
+ headers?: Record<string, string>;
332
+ expose?: boolean;
333
+ [key: string]: unknown;
334
+ }
335
+
336
+ export class WebspressoError extends Error {
337
+ name: string;
338
+ code?: string;
339
+ details?: unknown;
340
+ cause?: unknown;
341
+ status?: number;
342
+ constructor(message: string, options?: ErrorOptions);
343
+ }
344
+
345
+ export class HttpError extends WebspressoError {
346
+ status: number;
347
+ headers: Record<string, string>;
348
+ expose: boolean;
349
+ constructor(status?: number, message?: string, options?: ErrorOptions);
350
+ }
351
+
352
+ export class BadRequestError extends HttpError { constructor(message?: string, options?: ErrorOptions); }
353
+ export class UnauthorizedError extends HttpError { constructor(message?: string, options?: ErrorOptions); }
354
+ export class ForbiddenError extends HttpError { constructor(message?: string, options?: ErrorOptions); }
355
+ export class NotFoundError extends HttpError { constructor(message?: string, options?: ErrorOptions); }
356
+ export class MethodNotAllowedError extends HttpError { constructor(message?: string, options?: ErrorOptions); }
357
+ export class ConflictError extends HttpError { constructor(message?: string, options?: ErrorOptions); }
358
+ export class PayloadTooLargeError extends HttpError { constructor(message?: string, options?: ErrorOptions); }
359
+ export class UnsupportedMediaTypeError extends HttpError { constructor(message?: string, options?: ErrorOptions); }
360
+ export class UnprocessableEntityError extends HttpError { constructor(message?: string, options?: ErrorOptions); }
361
+ export class TooManyRequestsError extends HttpError { constructor(message?: string, options?: ErrorOptions); }
362
+
363
+ export class ValidationError extends HttpError {
364
+ fields: Record<string, string[] | string>;
365
+ constructor(message?: string, options?: ErrorOptions & { fields?: Record<string, string[] | string> });
366
+ }
367
+
368
+ export class ConfigurationError extends WebspressoError { constructor(message: string, options?: ErrorOptions); }
369
+ export class PluginError extends WebspressoError { plugin?: string | null; constructor(message: string, options?: ErrorOptions & { plugin?: string }); }
370
+ export class SecurityError extends HttpError { constructor(message?: string, options?: ErrorOptions); }
371
+ export class RequestError extends HttpError { constructor(status?: number, message?: string, options?: ErrorOptions); }
372
+ export class RequestAbortedError extends RequestError { constructor(message?: string, options?: ErrorOptions); }
373
+ export class RouterError extends WebspressoError { constructor(message: string, options?: ErrorOptions); }
374
+ export class RouteNotFoundError extends NotFoundError { path?: string; constructor(pathOrMessage?: string, options?: ErrorOptions); }
375
+ export class RouteGenerationError extends RouterError { constructor(message: string, options?: ErrorOptions); }
376
+
377
+ export function normalizeError(err: unknown, isDev?: boolean): HttpError | WebspressoError;
378
+ export function toErrorResponseObject(error: unknown, isDev?: boolean): Record<string, unknown>;
379
+
380
+ export function createCompressionMiddleware(options?: CompressionOptions): RequestHandler;
381
+
382
+ export namespace compression {
383
+ export function supportsBrotli(): boolean;
384
+ export function getDefaultSupportedEncodings(includeBrotli?: boolean): string[];
385
+ export function parseAcceptEncoding(header?: string | null): Array<{ encoding: string; q: number; index: number }>;
386
+ export function selectEncoding(acceptEncoding?: string | null, supportedEncodings?: string[]): string | null;
387
+ export function isCompressible(contentType?: string | null): boolean;
388
+ export function createCompressionStream(encoding: string, options?: CompressionOptions): unknown;
389
+ export function createCompressionMiddleware(options?: CompressionOptions): RequestHandler;
390
+ export function appendVary(res: unknown, field: string): void;
391
+ }
392
+
77
393
  export function resolveClientRuntime(options?: {
78
394
  clientRuntime?: {
79
395
  alpine?: boolean | Record<string, unknown>;
@@ -87,15 +403,31 @@ export const CLIENT_RUNTIME_BASE: string;
87
403
 
88
404
  export function attachDbMiddleware(req: Request, res: Response, next: NextFunction): void;
89
405
 
90
- export function getAppContext(): { db: DatabaseInstance | null };
406
+ export function getAppContext(): {
407
+ db: DatabaseInstance | null;
408
+ shutdownManager: ShutdownManager | null;
409
+ serviceRegistry: ServiceRegistry | null;
410
+ };
91
411
 
92
412
  export function getDb(): DatabaseInstance;
93
413
 
94
414
  export function hasDb(): boolean;
95
415
 
416
+ export function getShutdownManager(): ShutdownManager | null;
417
+
418
+ export function hasShutdownManager(): boolean;
419
+
420
+ export function getServiceRegistry(): ServiceRegistry | null;
421
+
422
+ export function hasServiceRegistry(): boolean;
423
+
96
424
  export function resetAppContext(): void;
97
425
 
98
- export function setAppContext(partial: { db?: DatabaseInstance | null }): void;
426
+ export function setAppContext(partial: {
427
+ db?: DatabaseInstance | null;
428
+ shutdownManager?: ShutdownManager | null;
429
+ serviceRegistry?: ServiceRegistry | null;
430
+ }): void;
99
431
 
100
432
  // --- File router ---
101
433
 
@@ -121,10 +453,31 @@ export function scanDirectory(
121
453
 
122
454
  export function loadI18n(pagesDir: string, routePath?: string): Record<string, unknown>;
123
455
 
456
+ export interface Translator {
457
+ (key: string, params?: Record<string, unknown> | string, defaultVal?: string | null): string;
458
+ locale: string;
459
+ translations: Record<string, unknown>;
460
+ number(num: number | string | null | undefined, options?: Intl.NumberFormatOptions): string;
461
+ formatNumber(num: number | string | null | undefined, options?: Intl.NumberFormatOptions): string;
462
+ currency(amount: number | string | null | undefined, currency?: string, options?: Intl.NumberFormatOptions): string;
463
+ formatCurrency(amount: number | string | null | undefined, currency?: string, options?: Intl.NumberFormatOptions): string;
464
+ date(date: Date | string | number | null | undefined, options?: Intl.DateTimeFormatOptions): string;
465
+ formatDate(date: Date | string | number | null | undefined, options?: Intl.DateTimeFormatOptions): string;
466
+ relativeTime(val: number | string | null | undefined, unit?: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions): string;
467
+ formatRelativeTime(val: number | string | null | undefined, unit?: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions): string;
468
+ plural(count: number | string, forms: Record<string, string>, params?: Record<string, unknown>): string;
469
+ }
470
+
471
+ export interface CreateTranslatorOptions {
472
+ locale?: string;
473
+ fallbackTranslations?: Record<string, unknown>;
474
+ fallbackLocale?: string;
475
+ }
476
+
124
477
  export function createTranslator(
125
- dictionaries: Record<string, unknown>,
126
- locale: string
127
- ): (key: string, params?: Record<string, unknown>) => string;
478
+ dictionaries?: Record<string, unknown>,
479
+ options?: CreateTranslatorOptions | string
480
+ ): Translator;
128
481
 
129
482
  export function detectLocale(
130
483
  req: Request,
@@ -265,10 +618,23 @@ export interface ScopeOptions {
265
618
  tenant?: string | null;
266
619
  }
267
620
 
268
- export interface ModelOptions {
621
+ // --- ORM: Type Inference Helpers ---
622
+
623
+ export type InferModel<T> = T extends ModelDefinition<infer TModel>
624
+ ? TModel
625
+ : T extends ZodObject<infer TShape>
626
+ ? import('zod').z.infer<T>
627
+ : T extends ZodTypeAny
628
+ ? import('zod').z.infer<T>
629
+ : Record<string, any>;
630
+
631
+ export type InferModelCreate<T> = Partial<InferModel<T>> & Record<string, any>;
632
+ export type InferModelUpdate<T> = Partial<InferModel<T>>;
633
+
634
+ export interface ModelOptions<TSchema extends ZodObject<any> = ZodObject<Record<string, ZodTypeAny>>> {
269
635
  name: string;
270
636
  table: string;
271
- schema: ZodObject<Record<string, ZodTypeAny>>;
637
+ schema: TSchema;
272
638
  primaryKey?: string;
273
639
  relations?: Record<string, RelationDefinition>;
274
640
  scopes?: ScopeOptions;
@@ -276,10 +642,16 @@ export interface ModelOptions {
276
642
  rest?: RestMetadata;
277
643
  hooks?: Record<string, (...args: unknown[]) => unknown>;
278
644
  hidden?: string[];
645
+ queryLimits?: {
646
+ maxLimit?: number;
647
+ defaultLimit?: number;
648
+ maxIncludes?: number;
649
+ maxFilterConditions?: number;
650
+ };
279
651
  cache?: boolean | 'auto' | 'smart' | { strategy: 'auto' | 'smart' };
280
652
  }
281
653
 
282
- export interface ModelDefinition {
654
+ export interface ModelDefinition<TModel = Record<string, any>> {
283
655
  name: string;
284
656
  table: string;
285
657
  schema: ZodObject<Record<string, ZodTypeAny>>;
@@ -296,7 +668,7 @@ export interface ModelDefinition {
296
668
  label: string;
297
669
  icon: string | null;
298
670
  customFields: Record<string, unknown>;
299
- queries: Record<string, (repo: Repository) => Promise<unknown>>;
671
+ queries: Record<string, (repo: Repository<TModel>) => Promise<unknown>>;
300
672
  };
301
673
  rest: {
302
674
  enabled: boolean;
@@ -304,15 +676,23 @@ export interface ModelDefinition {
304
676
  allowInclude: string[] | null;
305
677
  };
306
678
  hidden: string[];
679
+ queryLimits: {
680
+ maxLimit?: number;
681
+ defaultLimit?: number;
682
+ maxIncludes?: number;
683
+ maxFilterConditions?: number;
684
+ };
307
685
  hooks: Record<string, unknown>;
308
686
  cache?: boolean | 'auto' | 'smart' | { strategy: 'auto' | 'smart' };
309
687
  }
310
688
 
311
- export function defineModel(options: ModelOptions): ModelDefinition;
689
+ export function defineModel<TSchema extends ZodObject<any>>(
690
+ options: ModelOptions<TSchema>
691
+ ): ModelDefinition<import('zod').z.infer<TSchema>>;
312
692
 
313
- export function getModel(name: string): ModelDefinition | undefined;
693
+ export function getModel<T = Record<string, any>>(name: string): ModelDefinition<T> | undefined;
314
694
 
315
- export function getAllModels(): Map<string, ModelDefinition>;
695
+ export function getAllModels(): Map<string, ModelDefinition<any>>;
316
696
 
317
697
  export function hasModel(name: string): boolean;
318
698
 
@@ -340,58 +720,58 @@ export interface FindOptions {
340
720
  select?: string[];
341
721
  }
342
722
 
343
- export interface PaginatedResult {
344
- data: Record<string, unknown>[];
723
+ export interface PaginatedResult<T = Record<string, any>> {
724
+ data: T[];
345
725
  total: number;
346
726
  page: number;
347
727
  perPage: number;
348
728
  totalPages: number;
349
729
  }
350
730
 
351
- export interface QueryBuilder {
352
- where(column: string, value: unknown): this;
353
- where(column: string, operator: string, value: unknown): this;
354
- where(conditions: Record<string, unknown>): this;
731
+ export interface QueryBuilder<T = Record<string, any>> {
732
+ where(column: keyof T | string, value: unknown): this;
733
+ where(column: keyof T | string, operator: string, value: unknown): this;
734
+ where(conditions: Partial<T> | Record<string, unknown>): this;
355
735
  orWhere(...args: unknown[]): this;
356
- whereIn(column: string, values: unknown[]): this;
357
- whereBetween(column: string, range: [unknown, unknown]): this;
358
- select(...columns: string[]): this;
359
- orderBy(column: string, direction?: 'asc' | 'desc'): this;
736
+ whereIn(column: keyof T | string, values: unknown[]): this;
737
+ whereBetween(column: keyof T | string, range: [unknown, unknown]): this;
738
+ select(...columns: (keyof T | string)[]): this;
739
+ orderBy(column: keyof T | string, direction?: 'asc' | 'desc'): this;
360
740
  limit(n: number): this;
361
741
  offset(n: number): this;
362
742
  with(...relations: string[]): this;
363
743
  withTrashed(): this;
364
744
  onlyTrashed(): this;
365
- first(): Promise<Record<string, unknown> | null>;
366
- list(): Promise<Record<string, unknown>[]>;
367
- get(): Promise<Record<string, unknown>[]>;
745
+ first(): Promise<T | null>;
746
+ list(): Promise<T[]>;
747
+ get(): Promise<T[]>;
368
748
  count(): Promise<number>;
369
- paginate(page?: number, perPage?: number): Promise<PaginatedResult>;
749
+ paginate(page?: number, perPage?: number): Promise<PaginatedResult<T>>;
370
750
  exists(): Promise<boolean>;
371
- clone(): QueryBuilder;
751
+ clone(): QueryBuilder<T>;
372
752
  getWiths(): string[];
373
753
  delete(): Promise<number>;
374
- update(data: Record<string, unknown>): Promise<number>;
754
+ update(data: Partial<T> | Record<string, unknown>): Promise<number>;
375
755
  [key: string]: unknown;
376
756
  }
377
757
 
378
- export interface Repository {
379
- findById(id: string | number, options?: FindOptions): Promise<Record<string, unknown> | null>;
380
- findOne(conditions: Record<string, unknown>, options?: FindOptions): Promise<Record<string, unknown> | null>;
381
- findAll(options?: FindOptions): Promise<Record<string, unknown>[]>;
382
- create(data: Record<string, unknown>): Promise<Record<string, unknown>>;
383
- createMany(data: Record<string, unknown>[]): Promise<Record<string, unknown>[]>;
384
- update(id: string | number, data: Record<string, unknown>): Promise<Record<string, unknown> | null>;
385
- updateWhere(conditions: Record<string, unknown>, data: Record<string, unknown>): Promise<number>;
758
+ export interface Repository<T = Record<string, any>> {
759
+ findById(id: string | number, options?: FindOptions): Promise<T | null>;
760
+ findOne(conditions: Partial<T> | Record<string, unknown>, options?: FindOptions): Promise<T | null>;
761
+ findAll(options?: FindOptions): Promise<T[]>;
762
+ create(data: Partial<T> | Record<string, unknown>): Promise<T>;
763
+ createMany(data: (Partial<T> | Record<string, unknown>)[]): Promise<T[]>;
764
+ update(id: string | number, data: Partial<T> | Record<string, unknown>): Promise<T | null>;
765
+ updateWhere(conditions: Partial<T> | Record<string, unknown>, data: Partial<T> | Record<string, unknown>): Promise<number>;
386
766
  delete(id: string | number): Promise<boolean>;
387
767
  forceDelete(id: string | number): Promise<boolean>;
388
- restore(id: string | number): Promise<Record<string, unknown> | null>;
389
- query(): QueryBuilder;
768
+ restore(id: string | number): Promise<T | null>;
769
+ query(): QueryBuilder<T>;
390
770
  raw(sql: string, bindings?: unknown[]): Promise<unknown>;
391
- count(conditions?: Record<string, unknown>): Promise<number>;
392
- exists(conditions: Record<string, unknown>): Promise<boolean>;
393
- with(...relations: string[]): QueryBuilder;
394
- model: ModelDefinition;
771
+ count(conditions?: Partial<T> | Record<string, unknown>): Promise<number>;
772
+ exists(conditions: Partial<T> | Record<string, unknown>): Promise<boolean>;
773
+ with(...relations: string[]): QueryBuilder<T>;
774
+ model: ModelDefinition<T>;
395
775
  }
396
776
 
397
777
  // --- ORM: database ---
@@ -471,21 +851,24 @@ export interface MigrationManager {
471
851
 
472
852
  export interface TransactionContext {
473
853
  trx: Knex.Transaction;
474
- getRepository(modelName: string, scopeContext?: ScopeContext): Repository;
475
- createRepository(model: ModelDefinition, scopeContext?: ScopeContext): Repository;
854
+ getRepository<T = Record<string, any>>(modelName: string, scopeContext?: ScopeContext): Repository<T>;
855
+ createRepository<T = Record<string, any>>(model: ModelDefinition<T>, scopeContext?: ScopeContext): Repository<T>;
476
856
  }
477
857
 
478
858
  export interface DatabaseInstance {
479
859
  knex: Knex;
480
860
  migrate: MigrationManager;
481
- getModel(name: string): ModelDefinition;
861
+ getModel<T = Record<string, any>>(name: string): ModelDefinition<T>;
482
862
  hasModel(name: string): boolean;
483
- getAllModels(): ModelDefinition[];
484
- registerModel(model: ModelDefinition): void;
485
- getRepository(modelName: string, scopeContext?: ScopeContext): Repository;
486
- createRepository(model: ModelDefinition, scopeContext?: ScopeContext): Repository;
487
- query(modelName: string, scopeContext?: ScopeContext): QueryBuilder;
488
- transaction<T>(callback: (ctx: TransactionContext) => Promise<T>): Promise<T>;
863
+ getAllModels(): ModelDefinition<any>[];
864
+ registerModel<T = Record<string, any>>(model: ModelDefinition<T>): void;
865
+ getRepository<T = Record<string, any>>(modelName: string, scopeContext?: ScopeContext): Repository<T>;
866
+ createRepository<T = Record<string, any>>(model: ModelDefinition<T>, scopeContext?: ScopeContext): Repository<T>;
867
+ query<T = Record<string, any>>(modelName: string, scopeContext?: ScopeContext): QueryBuilder<T>;
868
+ transaction<T>(callback: (ctx: TransactionContext) => Promise<T>, scopeContext?: ScopeContext): Promise<T>;
869
+ runInTransaction<T>(callback: (ctx: TransactionContext) => Promise<T>, scopeContext?: ScopeContext): Promise<T>;
870
+ getAmbientTransaction(): Knex.Transaction | null;
871
+ hasActiveTransaction(): boolean;
489
872
  createSeeder(): unknown;
490
873
  cache: OrmCachePublicApi | null;
491
874
  destroy(): Promise<void>;
@@ -493,6 +876,12 @@ export interface DatabaseInstance {
493
876
 
494
877
  export function createDatabase(config: DatabaseConfig): DatabaseInstance;
495
878
 
879
+ export function runWithAmbientTransaction<T>(trx: Knex.Transaction, callback: () => Promise<T> | T): Promise<T>;
880
+ export function getAmbientTransaction(): Knex.Transaction | null;
881
+ export function hasAmbientTransaction(): boolean;
882
+ export function createTransactionContext(trx: Knex.Transaction, scopeContext?: ScopeContext): TransactionContext;
883
+ export function runTransaction<T>(knex: Knex, callback: (ctx: TransactionContext) => Promise<T>, scopeContext?: ScopeContext): Promise<T>;
884
+
496
885
  export function createMemoryCacheProvider(options?: OrmMemoryCacheOptions): {
497
886
  get(key: string): unknown;
498
887
  set(key: string, value: unknown, opts?: { tags?: string[]; ttlMs?: number }): void;
@@ -549,6 +938,36 @@ export function createEventContext(
549
938
  cancel(reason?: string): void;
550
939
  };
551
940
 
941
+ // --- ORM: Query Complexity & DoS Protection ---
942
+
943
+ export class QueryComplexityError extends BadRequestError {
944
+ code: string;
945
+ }
946
+
947
+ export const DEFAULT_QUERY_LIMITS: {
948
+ maxLimit: number;
949
+ defaultLimit: number;
950
+ maxIncludes: number;
951
+ maxFilterConditions: number;
952
+ };
953
+
954
+ export function resolveQueryLimits(model?: ModelDefinition): typeof DEFAULT_QUERY_LIMITS;
955
+
956
+ export function validateQueryComplexity(
957
+ model?: ModelDefinition,
958
+ params?: {
959
+ perPage?: number;
960
+ limit?: number;
961
+ includes?: string[];
962
+ filterCount?: number;
963
+ strict?: boolean;
964
+ }
965
+ ): {
966
+ limit: number;
967
+ perPage: number;
968
+ includes: string[];
969
+ };
970
+
552
971
  // --- Built-in plugins (subset re-exported from main index.js) ---
553
972
 
554
973
  export function schemaExplorerPlugin(options?: Record<string, unknown>): WebspressoPlugin;
@@ -649,6 +1068,56 @@ export interface CsrfPluginOptions {
649
1068
 
650
1069
  export function csrfPlugin(options?: CsrfPluginOptions): WebspressoPlugin;
651
1070
 
1071
+ export interface CorsPluginOptions {
1072
+ origin?: string | string[] | RegExp | ((origin: string, cb: (err: Error | null, allow?: boolean | string) => void) => void) | boolean;
1073
+ methods?: string | string[];
1074
+ allowedHeaders?: string | string[];
1075
+ exposedHeaders?: string | string[];
1076
+ credentials?: boolean;
1077
+ maxAge?: number;
1078
+ preflightContinue?: boolean;
1079
+ optionsSuccessStatus?: number;
1080
+ routes?: string | string[] | boolean;
1081
+ global?: boolean;
1082
+ }
1083
+
1084
+ export function corsPlugin(options?: CorsPluginOptions): WebspressoPlugin;
1085
+
1086
+ export interface BasicAuthCredentials {
1087
+ username: string;
1088
+ password?: string;
1089
+ [key: string]: unknown;
1090
+ }
1091
+
1092
+ export interface BasicAuthPluginOptions {
1093
+ /** Apply basic auth as a global middleware across the application. Default false. */
1094
+ global?: boolean;
1095
+ /** Static username-password dictionary. */
1096
+ users?: Record<string, string>;
1097
+ /** Custom async/sync credential verification function. */
1098
+ verify?: (
1099
+ username: string,
1100
+ password: string,
1101
+ req: Request
1102
+ ) => boolean | BasicAuthCredentials | Promise<boolean | BasicAuthCredentials>;
1103
+ /** Realm string included in WWW-Authenticate header. Default 'Restricted Area'. */
1104
+ realm?: string;
1105
+ /** Whether to send WWW-Authenticate header on 401. Default true. */
1106
+ challenge?: boolean;
1107
+ /** Route prefixes to protect when global: true. Default true (all routes). */
1108
+ routes?: string | string[] | boolean;
1109
+ /** Path prefixes to bypass basic authentication. */
1110
+ skipPaths?: string[];
1111
+ /** Custom predicate function to skip basic authentication. */
1112
+ skip?: (req: Request) => boolean;
1113
+ /** Custom 401 Unauthorized response handler, string, or object. */
1114
+ unauthorizedResponse?: ((req: Request, res: Response) => unknown) | string | Record<string, unknown>;
1115
+ /** Callback hook on successful authentication. */
1116
+ onAuthenticated?: (req: Request, user: BasicAuthCredentials) => void;
1117
+ }
1118
+
1119
+ export function basicAuthPlugin(options?: BasicAuthPluginOptions): WebspressoPlugin;
1120
+
652
1121
  export interface ContentPluginOptions {
653
1122
  db: import('./index').Database;
654
1123
  adminPath?: string;
@@ -708,6 +1177,169 @@ export function createLocalFileProvider(options?: {
708
1177
  publicBasePath?: string;
709
1178
  }): UploadStorageProvider;
710
1179
 
1180
+ // --- Realtime Layer Types ---
1181
+
1182
+ export interface RealtimeSubscriptionCallbacks {
1183
+ connected?: () => void;
1184
+ disconnected?: () => void;
1185
+ rejected?: (error: Error) => void;
1186
+ received?: (data: unknown) => void;
1187
+ }
1188
+
1189
+ export interface RealtimeSubscription {
1190
+ id: string;
1191
+ identifier: string;
1192
+ params: Record<string, unknown>;
1193
+ send(data: unknown): void;
1194
+ perform(action: string, data?: unknown): void;
1195
+ unsubscribe(): void;
1196
+ }
1197
+
1198
+ export interface RealtimeAdapter {
1199
+ capabilities?: {
1200
+ send?: boolean;
1201
+ perform?: boolean;
1202
+ multiplexing?: boolean;
1203
+ serverEvents?: boolean;
1204
+ binary?: boolean;
1205
+ [key: string]: boolean | undefined;
1206
+ };
1207
+ connect(context: { auth: Record<string, unknown>; client: RealtimeClient }): Promise<void>;
1208
+ disconnect(): void;
1209
+ isConnected(): boolean;
1210
+ subscribe(
1211
+ identifier: string,
1212
+ params: Record<string, unknown>,
1213
+ callbacks: RealtimeSubscriptionCallbacks
1214
+ ): {
1215
+ send?(data: unknown): void;
1216
+ perform?(action: string, data?: unknown): void;
1217
+ unsubscribe(): void;
1218
+ };
1219
+ send?(identifier: string, data: unknown, sub?: RealtimeSubscription): void;
1220
+ perform?(identifier: string, action: string, data?: unknown, sub?: RealtimeSubscription): void;
1221
+ unsubscribe?(identifier: string, params: Record<string, unknown>, sub?: RealtimeSubscription): void;
1222
+ on?(event: string, handler: (...args: any[]) => void): () => void;
1223
+ destroy?(): void;
1224
+ }
1225
+
1226
+ export type RealtimeAuthStrategy =
1227
+ | false
1228
+ | {
1229
+ strategy: 'cookie';
1230
+ }
1231
+ | {
1232
+ strategy: 'token';
1233
+ getToken: () => string | Promise<string>;
1234
+ refreshToken?: () => string | Promise<string>;
1235
+ onUnauthorized?: (error: Error) => void;
1236
+ }
1237
+ | {
1238
+ strategy: 'custom';
1239
+ resolve: () => Record<string, unknown> | Promise<Record<string, unknown>>;
1240
+ };
1241
+
1242
+ export interface RealtimeReconnectOptions {
1243
+ enabled?: boolean;
1244
+ maxAttempts?: number;
1245
+ baseDelay?: number;
1246
+ maxDelay?: number;
1247
+ factor?: number;
1248
+ jitter?: boolean;
1249
+ }
1250
+
1251
+ export interface RealtimeOptions {
1252
+ adapter: RealtimeAdapter;
1253
+ auth?: RealtimeAuthStrategy;
1254
+ autoConnect?: boolean;
1255
+ reconnect?: RealtimeReconnectOptions;
1256
+ isBrowser?: boolean;
1257
+ }
1258
+
1259
+ export interface RealtimeClient {
1260
+ capabilities: Record<string, boolean>;
1261
+ isConnected(): boolean;
1262
+ connect(overrideAuth?: Record<string, unknown> | null): Promise<void>;
1263
+ disconnect(): void;
1264
+ reconnect(): Promise<void>;
1265
+ reauthenticate(): Promise<void>;
1266
+ subscribe(
1267
+ identifier: string,
1268
+ params?: Record<string, unknown>,
1269
+ callbacks?: RealtimeSubscriptionCallbacks
1270
+ ): RealtimeSubscription;
1271
+ on(event: 'connected' | 'disconnected' | 'reconnecting' | 'reconnected' | 'unauthorized' | 'error', handler: (...args: any[]) => void): () => void;
1272
+ once(event: string, handler: (...args: any[]) => void): () => void;
1273
+ off(event: string, handler: (...args: any[]) => void): () => void;
1274
+ destroy(): void;
1275
+ }
1276
+
1277
+ export function createRealtime(options: RealtimeOptions): RealtimeClient;
1278
+ export function realtime(options: RealtimeOptions): RealtimeClient & { plugin: WebspressoPlugin };
1279
+ export function realtimePlugin(options: RealtimeOptions): WebspressoPlugin;
1280
+
1281
+ export interface WebSocketAdapterOptions {
1282
+ url: string | ((context: { auth: Record<string, unknown> }) => string | Promise<string>);
1283
+ authTransport?: 'query' | 'header' | 'protocol' | 'message';
1284
+ queryParamName?: string;
1285
+ protocols?: string | string[];
1286
+ WebSocket?: any;
1287
+ messages?: {
1288
+ subscribe?: (args: { identifier: string; params: Record<string, unknown> }) => unknown;
1289
+ unsubscribe?: (args: { identifier: string; params: Record<string, unknown> }) => unknown;
1290
+ perform?: (args: { identifier: string; action: string; data?: unknown }) => unknown;
1291
+ send?: (args: { identifier: string; data: unknown }) => unknown;
1292
+ auth?: (args: { token: string; metadata: Record<string, unknown> }) => unknown;
1293
+ };
1294
+ capabilities?: Record<string, boolean>;
1295
+ }
1296
+
1297
+ export function websocket(options: WebSocketAdapterOptions): RealtimeAdapter;
1298
+ export function createWebSocketAdapter(options: WebSocketAdapterOptions): RealtimeAdapter;
1299
+
1300
+ export interface SseAdapterOptions {
1301
+ url: string | ((context: { auth: Record<string, unknown> }) => string | Promise<string>);
1302
+ queryParamName?: string;
1303
+ EventSource?: any;
1304
+ capabilities?: Record<string, boolean>;
1305
+ }
1306
+
1307
+ export function sse(options: SseAdapterOptions): RealtimeAdapter;
1308
+ export function createSseAdapter(options: SseAdapterOptions): RealtimeAdapter;
1309
+
1310
+ export interface SocketIoAdapterOptions {
1311
+ url?: string | ((context: { auth: Record<string, unknown> }) => string | Promise<string>);
1312
+ path?: string;
1313
+ io?: any;
1314
+ socketOptions?: Record<string, unknown>;
1315
+ capabilities?: Record<string, boolean>;
1316
+ }
1317
+
1318
+ export function socketIo(options: SocketIoAdapterOptions): RealtimeAdapter;
1319
+ export function createSocketIoAdapter(options: SocketIoAdapterOptions): RealtimeAdapter;
1320
+
1321
+ export interface RedisAdapterOptions {
1322
+ pubClient?: any;
1323
+ subClient?: any;
1324
+ redis?: any;
1325
+ channelPrefix?: string;
1326
+ nodeId?: string;
1327
+ serialize?: (envelope: unknown) => string;
1328
+ deserialize?: (raw: string) => unknown;
1329
+ inMemory?: boolean;
1330
+ capabilities?: Record<string, boolean>;
1331
+ }
1332
+
1333
+ export interface RedisRealtimeAdapter extends RealtimeAdapter {
1334
+ publish(identifier: string, data: unknown): Promise<void>;
1335
+ perform(identifier: string, action: string, data?: unknown): Promise<void>;
1336
+ readonly isInMemory: boolean;
1337
+ readonly nodeId: string;
1338
+ }
1339
+
1340
+ export function redis(options?: RedisAdapterOptions): RedisRealtimeAdapter;
1341
+ export function createRedisAdapter(options?: RedisAdapterOptions): RedisRealtimeAdapter;
1342
+
711
1343
  // --- Application kernel (use `kernel.createApp`; not the SSR `createApp`) ---
712
1344
 
713
1345
  export type KernelEventSource = 'orm' | 'auth' | 'route' | 'plugin' | 'system';
@@ -807,3 +1439,242 @@ export interface WebspressoKernel {
807
1439
  }
808
1440
 
809
1441
  export const kernel: WebspressoKernel;
1442
+
1443
+ // --- Background Job Queue ---
1444
+
1445
+ export interface JobJSON {
1446
+ id: string | number;
1447
+ name: string;
1448
+ data: unknown;
1449
+ status: 'pending' | 'running' | 'completed' | 'failed';
1450
+ attempts: number;
1451
+ maxAttempts: number;
1452
+ backoff: unknown;
1453
+ priority: number;
1454
+ timeout: number;
1455
+ progress: number;
1456
+ result: unknown;
1457
+ error: string | null;
1458
+ createdAt: string;
1459
+ updatedAt: string;
1460
+ runAt: string;
1461
+ }
1462
+
1463
+ export class Job {
1464
+ id: string | number;
1465
+ name: string;
1466
+ data: any;
1467
+ status: 'pending' | 'running' | 'completed' | 'failed';
1468
+ attempts: number;
1469
+ maxAttempts: number;
1470
+ backoff: number | { type?: 'exponential' | 'fixed'; delay?: number };
1471
+ priority: number;
1472
+ timeout: number;
1473
+ progressValue: number;
1474
+ result: any;
1475
+ error: any;
1476
+ createdAt: Date;
1477
+ updatedAt: Date;
1478
+ runAt: Date;
1479
+ constructor(options?: Record<string, any>);
1480
+ progress(percent: number, message?: string): void;
1481
+ getNextRetryDelay(): number;
1482
+ toJSON(): JobJSON;
1483
+ }
1484
+
1485
+ export interface QueueAdapter {
1486
+ enqueue(job: Job): Promise<Job>;
1487
+ dequeue(jobNames?: string[]): Promise<Job | null>;
1488
+ complete(jobId: string | number, result?: unknown): Promise<Job | null>;
1489
+ fail(jobId: string | number, error?: unknown): Promise<Job | null>;
1490
+ retry(jobId: string | number, delayMs: number, error?: unknown): Promise<Job | null>;
1491
+ getJob(jobId: string | number): Promise<Job | null>;
1492
+ getStats(): Promise<{ pending: number; running: number; completed: number; failed: number; total: number }>;
1493
+ clear(): Promise<void>;
1494
+ }
1495
+
1496
+ export class MemoryQueueAdapter implements QueueAdapter {
1497
+ enqueue(job: Job): Promise<Job>;
1498
+ dequeue(jobNames?: string[]): Promise<Job | null>;
1499
+ complete(jobId: string | number, result?: unknown): Promise<Job | null>;
1500
+ fail(jobId: string | number, error?: unknown): Promise<Job | null>;
1501
+ retry(jobId: string | number, delayMs: number, error?: unknown): Promise<Job | null>;
1502
+ getJob(jobId: string | number): Promise<Job | null>;
1503
+ getStats(): Promise<{ pending: number; running: number; completed: number; failed: number; total: number }>;
1504
+ clear(): Promise<void>;
1505
+ }
1506
+
1507
+ export class DatabaseQueueAdapter implements QueueAdapter {
1508
+ constructor(options: { knex: Knex; tableName?: string });
1509
+ ensureTable(): Promise<void>;
1510
+ enqueue(job: Job): Promise<Job>;
1511
+ dequeue(jobNames?: string[]): Promise<Job | null>;
1512
+ complete(jobId: string | number, result?: unknown): Promise<Job | null>;
1513
+ fail(jobId: string | number, error?: unknown): Promise<Job | null>;
1514
+ retry(jobId: string | number, delayMs: number, error?: unknown): Promise<Job | null>;
1515
+ getJob(jobId: string | number): Promise<Job | null>;
1516
+ getStats(): Promise<{ pending: number; running: number; completed: number; failed: number; total: number }>;
1517
+ clear(): Promise<void>;
1518
+ }
1519
+
1520
+ export class RedisQueueAdapter implements QueueAdapter {
1521
+ constructor(options: { client: unknown; prefix?: string });
1522
+ enqueue(job: Job): Promise<Job>;
1523
+ dequeue(jobNames?: string[]): Promise<Job | null>;
1524
+ complete(jobId: string | number, result?: unknown): Promise<Job | null>;
1525
+ fail(jobId: string | number, error?: unknown): Promise<Job | null>;
1526
+ retry(jobId: string | number, delayMs: number, error?: unknown): Promise<Job | null>;
1527
+ getJob(jobId: string | number): Promise<Job | null>;
1528
+ getStats(): Promise<{ pending: number; running: number; completed: number; failed: number; total: number }>;
1529
+ clear(): Promise<void>;
1530
+ }
1531
+
1532
+ export interface DispatchOptions {
1533
+ delay?: number | Date;
1534
+ attempts?: number;
1535
+ priority?: number;
1536
+ timeout?: number;
1537
+ backoff?: number | { type?: 'exponential' | 'fixed'; delay?: number };
1538
+ }
1539
+
1540
+ export class QueueManager extends import('events').EventEmitter {
1541
+ adapter: QueueAdapter;
1542
+ concurrency: number;
1543
+ pollInterval: number;
1544
+ isRunning: boolean;
1545
+ isPaused: boolean;
1546
+ isDraining: boolean;
1547
+ constructor(options?: {
1548
+ adapter?: QueueAdapter;
1549
+ concurrency?: number;
1550
+ pollInterval?: number;
1551
+ jobsDir?: string | null;
1552
+ autoStart?: boolean;
1553
+ });
1554
+ define(name: string, handler: (job: Job, ctx: { queue: QueueManager }) => Promise<any> | any, options?: DispatchOptions): this;
1555
+ has(name: string): boolean;
1556
+ dispatch(name: string, data?: unknown, options?: DispatchOptions): Promise<Job>;
1557
+ start(): this;
1558
+ pause(): this;
1559
+ resume(): this;
1560
+ drain(timeoutMs?: number): Promise<void>;
1561
+ stop(): Promise<void>;
1562
+ getStats(): Promise<Record<string, unknown>>;
1563
+ getJob(jobId: string | number): Promise<Job | null>;
1564
+ loadJobsFromDirectory(dirPath: string): void;
1565
+ }
1566
+
1567
+ export function createQueueManager(options?: {
1568
+ adapter?: QueueAdapter;
1569
+ concurrency?: number;
1570
+ pollInterval?: number;
1571
+ jobsDir?: string | null;
1572
+ autoStart?: boolean;
1573
+ }): QueueManager;
1574
+
1575
+ export function queuePlugin(options?: {
1576
+ adapter?: 'memory' | 'database' | 'db' | 'redis' | QueueAdapter;
1577
+ db?: { knex: Knex };
1578
+ knex?: Knex;
1579
+ redisClient?: unknown;
1580
+ tableName?: string;
1581
+ concurrency?: number;
1582
+ pollInterval?: number;
1583
+ jobsDir?: string;
1584
+ autoStart?: boolean;
1585
+ }): PluginDefinition;
1586
+
1587
+ // --- SSR Streaming & Chunked Transfer ---
1588
+
1589
+ export interface HtmlStreamOptions {
1590
+ env: import('nunjucks').Environment;
1591
+ templatePath: string;
1592
+ context?: Record<string, unknown>;
1593
+ defer?: Record<string, Promise<unknown> | { promise: Promise<unknown>; template?: string }>;
1594
+ }
1595
+
1596
+ export interface RenderStreamOptions {
1597
+ env?: import('nunjucks').Environment;
1598
+ defer?: Record<string, Promise<unknown> | { promise: Promise<unknown>; template?: string }>;
1599
+ status?: number;
1600
+ flushImmediately?: boolean;
1601
+ }
1602
+
1603
+ export function createHtmlStream(options: HtmlStreamOptions): import('stream').Readable;
1604
+ export function renderStream(
1605
+ res: import('express').Response,
1606
+ templatePath: string,
1607
+ context?: Record<string, unknown>,
1608
+ options?: RenderStreamOptions
1609
+ ): Promise<void>;
1610
+
1611
+ export const ssr: {
1612
+ createHtmlStream: typeof createHtmlStream;
1613
+ renderStream: typeof renderStream;
1614
+ };
1615
+
1616
+ // --- Model Context Protocol (MCP) Plugin ---
1617
+
1618
+ export interface McpPluginOptions {
1619
+ path?: string;
1620
+ enabled?: boolean;
1621
+ auth?: {
1622
+ token?: string;
1623
+ localhostOnly?: boolean;
1624
+ verify?: (req: import('express').Request) => boolean | Promise<boolean>;
1625
+ };
1626
+ services?: boolean | {
1627
+ include?: string[];
1628
+ exclude?: string[];
1629
+ readOnly?: boolean;
1630
+ role?: string | string[];
1631
+ };
1632
+ orm?: boolean | {
1633
+ readOnly?: boolean;
1634
+ models?: string[];
1635
+ };
1636
+ system?: boolean;
1637
+ tools?: Array<{
1638
+ name: string;
1639
+ description?: string;
1640
+ inputSchema?: Record<string, unknown>;
1641
+ handler: (args: any, ctx: any) => Promise<any> | any;
1642
+ }>;
1643
+ resources?: Array<{
1644
+ uri?: string;
1645
+ uriTemplate?: string;
1646
+ name?: string;
1647
+ description?: string;
1648
+ mimeType?: string;
1649
+ handler: (uri: string, paramsOrCtx: any, ctx?: any) => Promise<any> | any;
1650
+ }>;
1651
+ prompts?: Array<{
1652
+ name: string;
1653
+ description?: string;
1654
+ arguments?: Array<{ name: string; description?: string; required?: boolean }>;
1655
+ handler: (args: any, ctx: any) => Promise<any> | any;
1656
+ }>;
1657
+ discovery?: boolean;
1658
+ name?: string;
1659
+ version?: string;
1660
+ instructions?: string;
1661
+ }
1662
+
1663
+ export class McpServer {
1664
+ name: string;
1665
+ version: string;
1666
+ instructions: string;
1667
+ constructor(options?: { name?: string; version?: string; instructions?: string; context?: Record<string, unknown> });
1668
+ registerTool(toolDef: any): this;
1669
+ registerTools(toolDefs: any[]): this;
1670
+ unregisterTool(name: string): boolean;
1671
+ registerResource(resourceDef: any): this;
1672
+ registerResourceTemplate(templateDef: any): this;
1673
+ registerPrompt(promptDef: any): this;
1674
+ handleMessage(message: any, callContext?: any): Promise<any>;
1675
+ }
1676
+
1677
+ export function mcpPlugin(options?: McpPluginOptions): WebspressoPlugin;
1678
+ export function createMcpServer(options?: McpPluginOptions): McpServer;
1679
+
1680
+