webspresso 0.0.90 → 0.0.91

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +358 -8
  2. package/bin/commands/service.js +193 -0
  3. package/bin/webspresso.js +2 -0
  4. package/core/auth/middleware.js +9 -0
  5. package/core/compression/compressible.js +51 -0
  6. package/core/compression/compression-stream.js +59 -0
  7. package/core/compression/index.js +25 -0
  8. package/core/compression/middleware.js +301 -0
  9. package/core/compression/negotiate.js +153 -0
  10. package/core/errors/base.js +31 -0
  11. package/core/errors/domain.js +193 -0
  12. package/core/errors/http.js +133 -0
  13. package/core/errors/index.js +77 -0
  14. package/core/errors/middleware.js +162 -0
  15. package/core/errors/normalize.js +171 -0
  16. package/core/orm/complexity.js +120 -0
  17. package/core/orm/events.js +64 -14
  18. package/core/orm/index.js +45 -10
  19. package/core/orm/model.js +2 -0
  20. package/core/orm/query-builder.js +30 -4
  21. package/core/orm/repository.js +57 -25
  22. package/core/orm/transaction.js +39 -5
  23. package/core/orm/utils.js +6 -3
  24. package/core/queue/adapters/database.js +284 -0
  25. package/core/queue/adapters/memory.js +155 -0
  26. package/core/queue/adapters/redis.js +336 -0
  27. package/core/queue/index.js +21 -0
  28. package/core/queue/job.js +103 -0
  29. package/core/queue/manager.js +387 -0
  30. package/core/realtime/adapters/redis.js +431 -0
  31. package/core/realtime/adapters/socket-io.js +203 -0
  32. package/core/realtime/adapters/sse.js +210 -0
  33. package/core/realtime/adapters/websocket.js +337 -0
  34. package/core/realtime/auth-manager.js +140 -0
  35. package/core/realtime/core.js +359 -0
  36. package/core/realtime/emitter.js +98 -0
  37. package/core/realtime/errors.js +71 -0
  38. package/core/realtime/index.js +79 -0
  39. package/core/realtime/reconnect-manager.js +118 -0
  40. package/core/realtime/registry.js +243 -0
  41. package/core/shutdown/http-adapter.js +134 -0
  42. package/core/shutdown/index.js +12 -0
  43. package/core/shutdown/shutdown-manager.js +312 -0
  44. package/core/ssr/index.js +13 -0
  45. package/core/ssr/stream.js +174 -0
  46. package/index.d.ts +829 -52
  47. package/index.js +103 -0
  48. package/package.json +22 -2
  49. package/plugins/admin-panel/api.js +37 -4
  50. package/plugins/admin-panel/app.js +57 -17
  51. package/plugins/admin-panel/client/parts/01-state-api-breadcrumb.js +4 -1
  52. package/plugins/admin-panel/client/parts/04-field-renderers.js +102 -58
  53. package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +37 -23
  54. package/plugins/admin-panel/client/parts/08-record-list.js +17 -24
  55. package/plugins/admin-panel/client/parts/09-record-form.js +168 -19
  56. package/plugins/admin-panel/core/admin-module.js +2 -2
  57. package/plugins/admin-panel/core/api-extensions.js +149 -0
  58. package/plugins/admin-panel/field-renderers/rich-text.js +3 -2
  59. package/plugins/admin-panel/index.js +90 -17
  60. package/plugins/admin-panel/modules/custom-pages.js +144 -45
  61. package/plugins/admin-panel/modules/menu.js +85 -0
  62. package/plugins/admin-panel/modules/user-management.js +5 -2
  63. package/plugins/admin-panel/vendor/mithril.min.js +1 -0
  64. package/plugins/admin-panel/vendor/quill.min.js +7 -0
  65. package/plugins/admin-panel/vendor/quill.snow.min.css +945 -0
  66. package/plugins/admin-panel/vendor/tailwind.min.js +65 -0
  67. package/plugins/basic-auth/index.js +293 -0
  68. package/plugins/data-exchange/export-xlsx.js +5 -1
  69. package/plugins/data-exchange/import.js +16 -2
  70. package/plugins/data-exchange/index.js +14 -0
  71. package/plugins/email/index.js +14 -1
  72. package/plugins/index.js +29 -0
  73. package/plugins/queue/index.js +108 -0
  74. package/plugins/realtime/index.js +150 -0
  75. package/plugins/recaptcha/index.js +11 -0
  76. package/plugins/redirect/index.js +9 -1
  77. package/plugins/rest-resources/index.js +280 -11
  78. package/plugins/upload/index.js +16 -0
  79. package/src/app-context.js +46 -6
  80. package/src/file-router.js +196 -21
  81. package/src/helpers.js +20 -2
  82. package/src/plugin-manager.js +124 -4
  83. package/src/server.js +304 -44
  84. package/src/services/builtins/auth.js +273 -0
  85. package/src/services/builtins/exchange.js +229 -0
  86. package/src/services/builtins/mail.js +134 -0
  87. package/src/services/builtins/media.js +196 -0
  88. package/src/services/builtins/system.js +221 -0
  89. package/src/services/discovery.js +140 -0
  90. package/src/services/executor.js +292 -0
  91. package/src/services/index.js +70 -0
  92. package/src/services/memoize.js +256 -0
  93. package/src/services/registry.js +288 -0
  94. package/src/services/validator.js +281 -0
package/index.d.ts CHANGED
@@ -48,15 +48,220 @@ export interface CreateAppOptions {
48
48
  timeout?: string | false;
49
49
  auth?: unknown;
50
50
  db?: DatabaseInstance | null;
51
+ /** HTTP response compression options */
52
+ compression?: boolean | CompressionOptions;
53
+ /** Shutdown and lifecycle configuration */
54
+ shutdown?: ShutdownOptions;
55
+ server?: {
56
+ port?: number;
57
+ shutdown?: ShutdownOptions;
58
+ compression?: boolean | CompressionOptions;
59
+ [key: string]: unknown;
60
+ };
51
61
  /** Opt-in Alpine / swup assets under `/__webspresso/client-runtime/*`. Env: WEBSPRESSO_ALPINE, WEBSPRESSO_SWUP. */
52
62
  clientRuntime?: {
53
63
  alpine?: boolean | Record<string, unknown>;
54
64
  swup?: boolean | Record<string, unknown>;
55
65
  };
66
+ /** Path to services directory (auto-discovered) */
67
+ servicesDir?: string;
68
+ services?: {
69
+ dir?: string;
70
+ };
56
71
  setupRoutes?: (app: Application, ctx: SetupRoutesContext) => void;
57
72
  [key: string]: unknown;
58
73
  }
59
74
 
75
+ export interface ServiceContext {
76
+ req?: Request;
77
+ res?: Response;
78
+ db?: DatabaseInstance | null;
79
+ service?: <T = unknown>(name: string, input?: unknown, options?: unknown) => Promise<T>;
80
+ [key: string]: unknown;
81
+ }
82
+
83
+ export type ServiceHandler<TInput = unknown, TOutput = unknown> = (
84
+ input: TInput,
85
+ ctx: ServiceContext
86
+ ) => Promise<TOutput> | TOutput;
87
+
88
+ export interface ServiceDefinition<TInput = unknown, TOutput = unknown> {
89
+ schema?: import('zod').ZodType<any> | Record<string, unknown> | ((input: unknown) => unknown) | null;
90
+ handler: ServiceHandler<TInput, TOutput>;
91
+ metadata?: Record<string, unknown>;
92
+ auth?: boolean | string | string[];
93
+ cache?: boolean | string | number;
94
+ timeout?: number;
95
+ transaction?: boolean;
96
+ [key: string]: unknown;
97
+ }
98
+
99
+ export class ServiceRegistry {
100
+ constructor(options?: { servicesDir?: string; isDev?: boolean; logger?: unknown });
101
+ readonly services: Map<string, ServiceDefinition>;
102
+ load(): this;
103
+ register(name: string, definition: ServiceDefinition | ServiceHandler): this;
104
+ get(name: string): ServiceDefinition | undefined;
105
+ has(name: string): boolean;
106
+ list(): string[];
107
+ invalidate(name: string, input?: unknown, ctx?: unknown): boolean;
108
+ clearCache(name?: string): boolean;
109
+ call<T = unknown>(name: string, input?: unknown, ctx?: unknown, options?: unknown): Promise<T>;
110
+ reload(): this;
111
+ }
112
+
113
+ export function createServiceRegistry(options?: {
114
+ servicesDir?: string;
115
+ isDev?: boolean;
116
+ logger?: unknown;
117
+ autoLoad?: boolean;
118
+ }): ServiceRegistry;
119
+
120
+ export type InferServiceInput<T> = T extends ServiceDefinition<infer TInput, any>
121
+ ? TInput
122
+ : T extends { schema: import('zod').ZodType<infer TInput> }
123
+ ? TInput
124
+ : T extends (input: infer TInput, ...args: any[]) => any
125
+ ? TInput
126
+ : unknown;
127
+
128
+ export type InferServiceOutput<T> = T extends ServiceDefinition<any, infer TOutput>
129
+ ? TOutput
130
+ : T extends (...args: any[]) => Promise<infer TOutput>
131
+ ? TOutput
132
+ : T extends (...args: any[]) => infer TOutput
133
+ ? TOutput
134
+ : unknown;
135
+
136
+ export function defineService<TInput = unknown, TOutput = unknown>(
137
+ definition: ServiceDefinition<TInput, TOutput> | ServiceHandler<TInput, TOutput>
138
+ ): ServiceDefinition<TInput, TOutput>;
139
+
140
+ export const service: typeof defineService;
141
+
142
+ export interface MemoizeOptions {
143
+ ttl?: string | number | boolean;
144
+ maxSize?: number;
145
+ key?: (input: unknown, ctx?: unknown) => string;
146
+ }
147
+
148
+ export interface MemoizedFunction<TArgs extends unknown[], TReturn> {
149
+ (...args: TArgs): Promise<TReturn>;
150
+ invalidate(...args: TArgs): boolean;
151
+ delete(...args: TArgs): boolean;
152
+ clear(): void;
153
+ has(...args: TArgs): boolean;
154
+ readonly size: number;
155
+ }
156
+
157
+ export function memoize<TArgs extends unknown[], TReturn>(
158
+ fn: (...args: TArgs) => Promise<TReturn> | TReturn,
159
+ options?: MemoizeOptions
160
+ ): MemoizedFunction<TArgs, TReturn>;
161
+
162
+ export function parseTtlMs(ttl: string | number | boolean): number;
163
+
164
+ export function stableCacheKey(input: unknown): string;
165
+
166
+ export function sanitizeInput<T = unknown>(input: T): T;
167
+
168
+ export function maskSensitiveData<T = unknown>(data: T, customRedactKeys?: string[]): T;
169
+
170
+ export function discoverServices(
171
+ servicesDir: string
172
+ ): Array<{ name: string; aliases: string[]; filePath: string; relativePath: string }>;
173
+
174
+ export function filePathToServiceName(relativePath: string): { name: string; aliases: string[] };
175
+
176
+ export function toCamelCase(str: string): string;
177
+
178
+ export function createAuthServices(options?: {
179
+ userModel?: string;
180
+ fields?: Record<string, string>;
181
+ }): Record<string, ServiceDefinition>;
182
+
183
+ export function createMailServices(options?: {
184
+ emailService: unknown;
185
+ registry?: unknown;
186
+ db?: unknown;
187
+ tableName?: string;
188
+ }): Record<string, ServiceDefinition>;
189
+
190
+ export function createMediaServices(options?: {
191
+ provider?: unknown;
192
+ destDir?: string;
193
+ publicBasePath?: string;
194
+ }): Record<string, ServiceDefinition>;
195
+
196
+ export function createSystemServices(options?: {
197
+ db?: unknown;
198
+ serviceRegistry?: unknown;
199
+ pluginManager?: unknown;
200
+ }): Record<string, ServiceDefinition>;
201
+
202
+ export function createExchangeServices(options?: {
203
+ db?: unknown;
204
+ }): Record<string, ServiceDefinition>;
205
+
206
+ export function validateServiceInput(schema: unknown, input: unknown, serviceName: string): unknown;
207
+
208
+ export function executeService(
209
+ registry: ServiceRegistry,
210
+ name: string,
211
+ input?: unknown,
212
+ ctx?: unknown,
213
+ options?: unknown
214
+ ): Promise<unknown>;
215
+
216
+ export interface CompressionOptions {
217
+ enabled?: boolean;
218
+ threshold?: number;
219
+ level?: number;
220
+ encodings?: string[];
221
+ filter?: (req: Request, res: Response) => boolean;
222
+ brotli?: Record<string, unknown>;
223
+ gzip?: Record<string, unknown>;
224
+ deflate?: Record<string, unknown>;
225
+ }
226
+
227
+ export interface ShutdownOptions {
228
+ enabled?: boolean;
229
+ mode?: 'graceful' | 'force';
230
+ timeout?: number;
231
+ signals?: string[];
232
+ logger?: unknown;
233
+ exitOnSignal?: boolean;
234
+ }
235
+
236
+ export interface ServerAdapter {
237
+ close(): Promise<void>;
238
+ forceClose?(): void;
239
+ closeIdleConnections?(): void;
240
+ }
241
+
242
+ export class NodeHttpAdapter implements ServerAdapter {
243
+ constructor(server: unknown);
244
+ close(): Promise<void>;
245
+ forceClose(): void;
246
+ closeIdleConnections(): void;
247
+ destroy(): void;
248
+ readonly isListening: boolean;
249
+ }
250
+
251
+ export class ShutdownManager {
252
+ constructor(options?: ShutdownOptions);
253
+ readonly isShuttingDown: boolean;
254
+ readonly isClosed: boolean;
255
+ mode: 'graceful' | 'force';
256
+ timeout: number;
257
+ onShutdown(fn: () => Promise<void> | void): this;
258
+ registerDisposer(nameOrFn: string | (() => Promise<void> | void), maybeFn?: () => Promise<void> | void): this;
259
+ registerAdapter(adapter: ServerAdapter): this;
260
+ enableShutdownHooks(): this;
261
+ disableShutdownHooks(): this;
262
+ close(reason?: string): Promise<void>;
263
+ }
264
+
60
265
  export interface SetupRoutesContext {
61
266
  nunjucksEnv: unknown;
62
267
  authMiddleware?: RequestHandler;
@@ -65,15 +270,96 @@ export interface SetupRoutesContext {
65
270
  clientRuntime: { alpine: boolean; swup: boolean };
66
271
  }
67
272
 
273
+ export interface WebspressoApplication extends Application {
274
+ server?: unknown;
275
+ readonly isShuttingDown: boolean;
276
+ shutdownManager: ShutdownManager;
277
+ onShutdown(fn: () => Promise<void> | void): this;
278
+ close(reason?: string): Promise<void>;
279
+ enableShutdownHooks(): this;
280
+ disableShutdownHooks(): this;
281
+ setErrorHandler(handler: (error: unknown, req: Request, res: Response, next: NextFunction) => Promise<unknown> | unknown): this;
282
+ }
283
+
68
284
  export interface CreateAppResult {
69
- app: Application;
285
+ app: WebspressoApplication;
70
286
  nunjucksEnv: unknown;
71
287
  pluginManager: PluginManager;
72
288
  authMiddleware?: RequestHandler;
289
+ shutdownManager: ShutdownManager;
73
290
  }
74
291
 
75
292
  export function createApp(options?: CreateAppOptions): CreateAppResult;
76
293
 
294
+ // --- Exceptions & Errors ---
295
+
296
+ export interface ErrorOptions {
297
+ code?: string;
298
+ details?: unknown;
299
+ cause?: unknown;
300
+ status?: number;
301
+ headers?: Record<string, string>;
302
+ expose?: boolean;
303
+ [key: string]: unknown;
304
+ }
305
+
306
+ export class WebspressoError extends Error {
307
+ name: string;
308
+ code?: string;
309
+ details?: unknown;
310
+ cause?: unknown;
311
+ status?: number;
312
+ constructor(message: string, options?: ErrorOptions);
313
+ }
314
+
315
+ export class HttpError extends WebspressoError {
316
+ status: number;
317
+ headers: Record<string, string>;
318
+ expose: boolean;
319
+ constructor(status?: number, message?: string, options?: ErrorOptions);
320
+ }
321
+
322
+ export class BadRequestError extends HttpError { constructor(message?: string, options?: ErrorOptions); }
323
+ export class UnauthorizedError extends HttpError { constructor(message?: string, options?: ErrorOptions); }
324
+ export class ForbiddenError extends HttpError { constructor(message?: string, options?: ErrorOptions); }
325
+ export class NotFoundError extends HttpError { constructor(message?: string, options?: ErrorOptions); }
326
+ export class MethodNotAllowedError extends HttpError { constructor(message?: string, options?: ErrorOptions); }
327
+ export class ConflictError extends HttpError { constructor(message?: string, options?: ErrorOptions); }
328
+ export class PayloadTooLargeError extends HttpError { constructor(message?: string, options?: ErrorOptions); }
329
+ export class UnsupportedMediaTypeError extends HttpError { constructor(message?: string, options?: ErrorOptions); }
330
+ export class UnprocessableEntityError extends HttpError { constructor(message?: string, options?: ErrorOptions); }
331
+ export class TooManyRequestsError extends HttpError { constructor(message?: string, options?: ErrorOptions); }
332
+
333
+ export class ValidationError extends HttpError {
334
+ fields: Record<string, string[] | string>;
335
+ constructor(message?: string, options?: ErrorOptions & { fields?: Record<string, string[] | string> });
336
+ }
337
+
338
+ export class ConfigurationError extends WebspressoError { constructor(message: string, options?: ErrorOptions); }
339
+ export class PluginError extends WebspressoError { plugin?: string | null; constructor(message: string, options?: ErrorOptions & { plugin?: string }); }
340
+ export class SecurityError extends HttpError { constructor(message?: string, options?: ErrorOptions); }
341
+ export class RequestError extends HttpError { constructor(status?: number, message?: string, options?: ErrorOptions); }
342
+ export class RequestAbortedError extends RequestError { constructor(message?: string, options?: ErrorOptions); }
343
+ export class RouterError extends WebspressoError { constructor(message: string, options?: ErrorOptions); }
344
+ export class RouteNotFoundError extends NotFoundError { path?: string; constructor(pathOrMessage?: string, options?: ErrorOptions); }
345
+ export class RouteGenerationError extends RouterError { constructor(message: string, options?: ErrorOptions); }
346
+
347
+ export function normalizeError(err: unknown, isDev?: boolean): HttpError | WebspressoError;
348
+ export function toErrorResponseObject(error: unknown, isDev?: boolean): Record<string, unknown>;
349
+
350
+ export function createCompressionMiddleware(options?: CompressionOptions): RequestHandler;
351
+
352
+ export namespace compression {
353
+ export function supportsBrotli(): boolean;
354
+ export function getDefaultSupportedEncodings(includeBrotli?: boolean): string[];
355
+ export function parseAcceptEncoding(header?: string | null): Array<{ encoding: string; q: number; index: number }>;
356
+ export function selectEncoding(acceptEncoding?: string | null, supportedEncodings?: string[]): string | null;
357
+ export function isCompressible(contentType?: string | null): boolean;
358
+ export function createCompressionStream(encoding: string, options?: CompressionOptions): unknown;
359
+ export function createCompressionMiddleware(options?: CompressionOptions): RequestHandler;
360
+ export function appendVary(res: unknown, field: string): void;
361
+ }
362
+
77
363
  export function resolveClientRuntime(options?: {
78
364
  clientRuntime?: {
79
365
  alpine?: boolean | Record<string, unknown>;
@@ -87,15 +373,31 @@ export const CLIENT_RUNTIME_BASE: string;
87
373
 
88
374
  export function attachDbMiddleware(req: Request, res: Response, next: NextFunction): void;
89
375
 
90
- export function getAppContext(): { db: DatabaseInstance | null };
376
+ export function getAppContext(): {
377
+ db: DatabaseInstance | null;
378
+ shutdownManager: ShutdownManager | null;
379
+ serviceRegistry: ServiceRegistry | null;
380
+ };
91
381
 
92
382
  export function getDb(): DatabaseInstance;
93
383
 
94
384
  export function hasDb(): boolean;
95
385
 
386
+ export function getShutdownManager(): ShutdownManager | null;
387
+
388
+ export function hasShutdownManager(): boolean;
389
+
390
+ export function getServiceRegistry(): ServiceRegistry | null;
391
+
392
+ export function hasServiceRegistry(): boolean;
393
+
96
394
  export function resetAppContext(): void;
97
395
 
98
- export function setAppContext(partial: { db?: DatabaseInstance | null }): void;
396
+ export function setAppContext(partial: {
397
+ db?: DatabaseInstance | null;
398
+ shutdownManager?: ShutdownManager | null;
399
+ serviceRegistry?: ServiceRegistry | null;
400
+ }): void;
99
401
 
100
402
  // --- File router ---
101
403
 
@@ -121,10 +423,31 @@ export function scanDirectory(
121
423
 
122
424
  export function loadI18n(pagesDir: string, routePath?: string): Record<string, unknown>;
123
425
 
426
+ export interface Translator {
427
+ (key: string, params?: Record<string, unknown> | string, defaultVal?: string | null): string;
428
+ locale: string;
429
+ translations: Record<string, unknown>;
430
+ number(num: number | string | null | undefined, options?: Intl.NumberFormatOptions): string;
431
+ formatNumber(num: number | string | null | undefined, options?: Intl.NumberFormatOptions): string;
432
+ currency(amount: number | string | null | undefined, currency?: string, options?: Intl.NumberFormatOptions): string;
433
+ formatCurrency(amount: number | string | null | undefined, currency?: string, options?: Intl.NumberFormatOptions): string;
434
+ date(date: Date | string | number | null | undefined, options?: Intl.DateTimeFormatOptions): string;
435
+ formatDate(date: Date | string | number | null | undefined, options?: Intl.DateTimeFormatOptions): string;
436
+ relativeTime(val: number | string | null | undefined, unit?: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions): string;
437
+ formatRelativeTime(val: number | string | null | undefined, unit?: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions): string;
438
+ plural(count: number | string, forms: Record<string, string>, params?: Record<string, unknown>): string;
439
+ }
440
+
441
+ export interface CreateTranslatorOptions {
442
+ locale?: string;
443
+ fallbackTranslations?: Record<string, unknown>;
444
+ fallbackLocale?: string;
445
+ }
446
+
124
447
  export function createTranslator(
125
- dictionaries: Record<string, unknown>,
126
- locale: string
127
- ): (key: string, params?: Record<string, unknown>) => string;
448
+ dictionaries?: Record<string, unknown>,
449
+ options?: CreateTranslatorOptions | string
450
+ ): Translator;
128
451
 
129
452
  export function detectLocale(
130
453
  req: Request,
@@ -265,10 +588,23 @@ export interface ScopeOptions {
265
588
  tenant?: string | null;
266
589
  }
267
590
 
268
- export interface ModelOptions {
591
+ // --- ORM: Type Inference Helpers ---
592
+
593
+ export type InferModel<T> = T extends ModelDefinition<infer TModel>
594
+ ? TModel
595
+ : T extends ZodObject<infer TShape>
596
+ ? import('zod').z.infer<T>
597
+ : T extends ZodTypeAny
598
+ ? import('zod').z.infer<T>
599
+ : Record<string, any>;
600
+
601
+ export type InferModelCreate<T> = Partial<InferModel<T>> & Record<string, any>;
602
+ export type InferModelUpdate<T> = Partial<InferModel<T>>;
603
+
604
+ export interface ModelOptions<TSchema extends ZodObject<any> = ZodObject<Record<string, ZodTypeAny>>> {
269
605
  name: string;
270
606
  table: string;
271
- schema: ZodObject<Record<string, ZodTypeAny>>;
607
+ schema: TSchema;
272
608
  primaryKey?: string;
273
609
  relations?: Record<string, RelationDefinition>;
274
610
  scopes?: ScopeOptions;
@@ -276,10 +612,16 @@ export interface ModelOptions {
276
612
  rest?: RestMetadata;
277
613
  hooks?: Record<string, (...args: unknown[]) => unknown>;
278
614
  hidden?: string[];
615
+ queryLimits?: {
616
+ maxLimit?: number;
617
+ defaultLimit?: number;
618
+ maxIncludes?: number;
619
+ maxFilterConditions?: number;
620
+ };
279
621
  cache?: boolean | 'auto' | 'smart' | { strategy: 'auto' | 'smart' };
280
622
  }
281
623
 
282
- export interface ModelDefinition {
624
+ export interface ModelDefinition<TModel = Record<string, any>> {
283
625
  name: string;
284
626
  table: string;
285
627
  schema: ZodObject<Record<string, ZodTypeAny>>;
@@ -296,7 +638,7 @@ export interface ModelDefinition {
296
638
  label: string;
297
639
  icon: string | null;
298
640
  customFields: Record<string, unknown>;
299
- queries: Record<string, (repo: Repository) => Promise<unknown>>;
641
+ queries: Record<string, (repo: Repository<TModel>) => Promise<unknown>>;
300
642
  };
301
643
  rest: {
302
644
  enabled: boolean;
@@ -304,15 +646,23 @@ export interface ModelDefinition {
304
646
  allowInclude: string[] | null;
305
647
  };
306
648
  hidden: string[];
649
+ queryLimits: {
650
+ maxLimit?: number;
651
+ defaultLimit?: number;
652
+ maxIncludes?: number;
653
+ maxFilterConditions?: number;
654
+ };
307
655
  hooks: Record<string, unknown>;
308
656
  cache?: boolean | 'auto' | 'smart' | { strategy: 'auto' | 'smart' };
309
657
  }
310
658
 
311
- export function defineModel(options: ModelOptions): ModelDefinition;
659
+ export function defineModel<TSchema extends ZodObject<any>>(
660
+ options: ModelOptions<TSchema>
661
+ ): ModelDefinition<import('zod').z.infer<TSchema>>;
312
662
 
313
- export function getModel(name: string): ModelDefinition | undefined;
663
+ export function getModel<T = Record<string, any>>(name: string): ModelDefinition<T> | undefined;
314
664
 
315
- export function getAllModels(): Map<string, ModelDefinition>;
665
+ export function getAllModels(): Map<string, ModelDefinition<any>>;
316
666
 
317
667
  export function hasModel(name: string): boolean;
318
668
 
@@ -340,58 +690,58 @@ export interface FindOptions {
340
690
  select?: string[];
341
691
  }
342
692
 
343
- export interface PaginatedResult {
344
- data: Record<string, unknown>[];
693
+ export interface PaginatedResult<T = Record<string, any>> {
694
+ data: T[];
345
695
  total: number;
346
696
  page: number;
347
697
  perPage: number;
348
698
  totalPages: number;
349
699
  }
350
700
 
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;
701
+ export interface QueryBuilder<T = Record<string, any>> {
702
+ where(column: keyof T | string, value: unknown): this;
703
+ where(column: keyof T | string, operator: string, value: unknown): this;
704
+ where(conditions: Partial<T> | Record<string, unknown>): this;
355
705
  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;
706
+ whereIn(column: keyof T | string, values: unknown[]): this;
707
+ whereBetween(column: keyof T | string, range: [unknown, unknown]): this;
708
+ select(...columns: (keyof T | string)[]): this;
709
+ orderBy(column: keyof T | string, direction?: 'asc' | 'desc'): this;
360
710
  limit(n: number): this;
361
711
  offset(n: number): this;
362
712
  with(...relations: string[]): this;
363
713
  withTrashed(): this;
364
714
  onlyTrashed(): this;
365
- first(): Promise<Record<string, unknown> | null>;
366
- list(): Promise<Record<string, unknown>[]>;
367
- get(): Promise<Record<string, unknown>[]>;
715
+ first(): Promise<T | null>;
716
+ list(): Promise<T[]>;
717
+ get(): Promise<T[]>;
368
718
  count(): Promise<number>;
369
- paginate(page?: number, perPage?: number): Promise<PaginatedResult>;
719
+ paginate(page?: number, perPage?: number): Promise<PaginatedResult<T>>;
370
720
  exists(): Promise<boolean>;
371
- clone(): QueryBuilder;
721
+ clone(): QueryBuilder<T>;
372
722
  getWiths(): string[];
373
723
  delete(): Promise<number>;
374
- update(data: Record<string, unknown>): Promise<number>;
724
+ update(data: Partial<T> | Record<string, unknown>): Promise<number>;
375
725
  [key: string]: unknown;
376
726
  }
377
727
 
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>;
728
+ export interface Repository<T = Record<string, any>> {
729
+ findById(id: string | number, options?: FindOptions): Promise<T | null>;
730
+ findOne(conditions: Partial<T> | Record<string, unknown>, options?: FindOptions): Promise<T | null>;
731
+ findAll(options?: FindOptions): Promise<T[]>;
732
+ create(data: Partial<T> | Record<string, unknown>): Promise<T>;
733
+ createMany(data: (Partial<T> | Record<string, unknown>)[]): Promise<T[]>;
734
+ update(id: string | number, data: Partial<T> | Record<string, unknown>): Promise<T | null>;
735
+ updateWhere(conditions: Partial<T> | Record<string, unknown>, data: Partial<T> | Record<string, unknown>): Promise<number>;
386
736
  delete(id: string | number): Promise<boolean>;
387
737
  forceDelete(id: string | number): Promise<boolean>;
388
- restore(id: string | number): Promise<Record<string, unknown> | null>;
389
- query(): QueryBuilder;
738
+ restore(id: string | number): Promise<T | null>;
739
+ query(): QueryBuilder<T>;
390
740
  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;
741
+ count(conditions?: Partial<T> | Record<string, unknown>): Promise<number>;
742
+ exists(conditions: Partial<T> | Record<string, unknown>): Promise<boolean>;
743
+ with(...relations: string[]): QueryBuilder<T>;
744
+ model: ModelDefinition<T>;
395
745
  }
396
746
 
397
747
  // --- ORM: database ---
@@ -471,21 +821,24 @@ export interface MigrationManager {
471
821
 
472
822
  export interface TransactionContext {
473
823
  trx: Knex.Transaction;
474
- getRepository(modelName: string, scopeContext?: ScopeContext): Repository;
475
- createRepository(model: ModelDefinition, scopeContext?: ScopeContext): Repository;
824
+ getRepository<T = Record<string, any>>(modelName: string, scopeContext?: ScopeContext): Repository<T>;
825
+ createRepository<T = Record<string, any>>(model: ModelDefinition<T>, scopeContext?: ScopeContext): Repository<T>;
476
826
  }
477
827
 
478
828
  export interface DatabaseInstance {
479
829
  knex: Knex;
480
830
  migrate: MigrationManager;
481
- getModel(name: string): ModelDefinition;
831
+ getModel<T = Record<string, any>>(name: string): ModelDefinition<T>;
482
832
  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>;
833
+ getAllModels(): ModelDefinition<any>[];
834
+ registerModel<T = Record<string, any>>(model: ModelDefinition<T>): void;
835
+ getRepository<T = Record<string, any>>(modelName: string, scopeContext?: ScopeContext): Repository<T>;
836
+ createRepository<T = Record<string, any>>(model: ModelDefinition<T>, scopeContext?: ScopeContext): Repository<T>;
837
+ query<T = Record<string, any>>(modelName: string, scopeContext?: ScopeContext): QueryBuilder<T>;
838
+ transaction<T>(callback: (ctx: TransactionContext) => Promise<T>, scopeContext?: ScopeContext): Promise<T>;
839
+ runInTransaction<T>(callback: (ctx: TransactionContext) => Promise<T>, scopeContext?: ScopeContext): Promise<T>;
840
+ getAmbientTransaction(): Knex.Transaction | null;
841
+ hasActiveTransaction(): boolean;
489
842
  createSeeder(): unknown;
490
843
  cache: OrmCachePublicApi | null;
491
844
  destroy(): Promise<void>;
@@ -493,6 +846,12 @@ export interface DatabaseInstance {
493
846
 
494
847
  export function createDatabase(config: DatabaseConfig): DatabaseInstance;
495
848
 
849
+ export function runWithAmbientTransaction<T>(trx: Knex.Transaction, callback: () => Promise<T> | T): Promise<T>;
850
+ export function getAmbientTransaction(): Knex.Transaction | null;
851
+ export function hasAmbientTransaction(): boolean;
852
+ export function createTransactionContext(trx: Knex.Transaction, scopeContext?: ScopeContext): TransactionContext;
853
+ export function runTransaction<T>(knex: Knex, callback: (ctx: TransactionContext) => Promise<T>, scopeContext?: ScopeContext): Promise<T>;
854
+
496
855
  export function createMemoryCacheProvider(options?: OrmMemoryCacheOptions): {
497
856
  get(key: string): unknown;
498
857
  set(key: string, value: unknown, opts?: { tags?: string[]; ttlMs?: number }): void;
@@ -549,6 +908,36 @@ export function createEventContext(
549
908
  cancel(reason?: string): void;
550
909
  };
551
910
 
911
+ // --- ORM: Query Complexity & DoS Protection ---
912
+
913
+ export class QueryComplexityError extends BadRequestError {
914
+ code: string;
915
+ }
916
+
917
+ export const DEFAULT_QUERY_LIMITS: {
918
+ maxLimit: number;
919
+ defaultLimit: number;
920
+ maxIncludes: number;
921
+ maxFilterConditions: number;
922
+ };
923
+
924
+ export function resolveQueryLimits(model?: ModelDefinition): typeof DEFAULT_QUERY_LIMITS;
925
+
926
+ export function validateQueryComplexity(
927
+ model?: ModelDefinition,
928
+ params?: {
929
+ perPage?: number;
930
+ limit?: number;
931
+ includes?: string[];
932
+ filterCount?: number;
933
+ strict?: boolean;
934
+ }
935
+ ): {
936
+ limit: number;
937
+ perPage: number;
938
+ includes: string[];
939
+ };
940
+
552
941
  // --- Built-in plugins (subset re-exported from main index.js) ---
553
942
 
554
943
  export function schemaExplorerPlugin(options?: Record<string, unknown>): WebspressoPlugin;
@@ -649,6 +1038,56 @@ export interface CsrfPluginOptions {
649
1038
 
650
1039
  export function csrfPlugin(options?: CsrfPluginOptions): WebspressoPlugin;
651
1040
 
1041
+ export interface CorsPluginOptions {
1042
+ origin?: string | string[] | RegExp | ((origin: string, cb: (err: Error | null, allow?: boolean | string) => void) => void) | boolean;
1043
+ methods?: string | string[];
1044
+ allowedHeaders?: string | string[];
1045
+ exposedHeaders?: string | string[];
1046
+ credentials?: boolean;
1047
+ maxAge?: number;
1048
+ preflightContinue?: boolean;
1049
+ optionsSuccessStatus?: number;
1050
+ routes?: string | string[] | boolean;
1051
+ global?: boolean;
1052
+ }
1053
+
1054
+ export function corsPlugin(options?: CorsPluginOptions): WebspressoPlugin;
1055
+
1056
+ export interface BasicAuthCredentials {
1057
+ username: string;
1058
+ password?: string;
1059
+ [key: string]: unknown;
1060
+ }
1061
+
1062
+ export interface BasicAuthPluginOptions {
1063
+ /** Apply basic auth as a global middleware across the application. Default false. */
1064
+ global?: boolean;
1065
+ /** Static username-password dictionary. */
1066
+ users?: Record<string, string>;
1067
+ /** Custom async/sync credential verification function. */
1068
+ verify?: (
1069
+ username: string,
1070
+ password: string,
1071
+ req: Request
1072
+ ) => boolean | BasicAuthCredentials | Promise<boolean | BasicAuthCredentials>;
1073
+ /** Realm string included in WWW-Authenticate header. Default 'Restricted Area'. */
1074
+ realm?: string;
1075
+ /** Whether to send WWW-Authenticate header on 401. Default true. */
1076
+ challenge?: boolean;
1077
+ /** Route prefixes to protect when global: true. Default true (all routes). */
1078
+ routes?: string | string[] | boolean;
1079
+ /** Path prefixes to bypass basic authentication. */
1080
+ skipPaths?: string[];
1081
+ /** Custom predicate function to skip basic authentication. */
1082
+ skip?: (req: Request) => boolean;
1083
+ /** Custom 401 Unauthorized response handler, string, or object. */
1084
+ unauthorizedResponse?: ((req: Request, res: Response) => unknown) | string | Record<string, unknown>;
1085
+ /** Callback hook on successful authentication. */
1086
+ onAuthenticated?: (req: Request, user: BasicAuthCredentials) => void;
1087
+ }
1088
+
1089
+ export function basicAuthPlugin(options?: BasicAuthPluginOptions): WebspressoPlugin;
1090
+
652
1091
  export interface ContentPluginOptions {
653
1092
  db: import('./index').Database;
654
1093
  adminPath?: string;
@@ -708,6 +1147,169 @@ export function createLocalFileProvider(options?: {
708
1147
  publicBasePath?: string;
709
1148
  }): UploadStorageProvider;
710
1149
 
1150
+ // --- Realtime Layer Types ---
1151
+
1152
+ export interface RealtimeSubscriptionCallbacks {
1153
+ connected?: () => void;
1154
+ disconnected?: () => void;
1155
+ rejected?: (error: Error) => void;
1156
+ received?: (data: unknown) => void;
1157
+ }
1158
+
1159
+ export interface RealtimeSubscription {
1160
+ id: string;
1161
+ identifier: string;
1162
+ params: Record<string, unknown>;
1163
+ send(data: unknown): void;
1164
+ perform(action: string, data?: unknown): void;
1165
+ unsubscribe(): void;
1166
+ }
1167
+
1168
+ export interface RealtimeAdapter {
1169
+ capabilities?: {
1170
+ send?: boolean;
1171
+ perform?: boolean;
1172
+ multiplexing?: boolean;
1173
+ serverEvents?: boolean;
1174
+ binary?: boolean;
1175
+ [key: string]: boolean | undefined;
1176
+ };
1177
+ connect(context: { auth: Record<string, unknown>; client: RealtimeClient }): Promise<void>;
1178
+ disconnect(): void;
1179
+ isConnected(): boolean;
1180
+ subscribe(
1181
+ identifier: string,
1182
+ params: Record<string, unknown>,
1183
+ callbacks: RealtimeSubscriptionCallbacks
1184
+ ): {
1185
+ send?(data: unknown): void;
1186
+ perform?(action: string, data?: unknown): void;
1187
+ unsubscribe(): void;
1188
+ };
1189
+ send?(identifier: string, data: unknown, sub?: RealtimeSubscription): void;
1190
+ perform?(identifier: string, action: string, data?: unknown, sub?: RealtimeSubscription): void;
1191
+ unsubscribe?(identifier: string, params: Record<string, unknown>, sub?: RealtimeSubscription): void;
1192
+ on?(event: string, handler: (...args: any[]) => void): () => void;
1193
+ destroy?(): void;
1194
+ }
1195
+
1196
+ export type RealtimeAuthStrategy =
1197
+ | false
1198
+ | {
1199
+ strategy: 'cookie';
1200
+ }
1201
+ | {
1202
+ strategy: 'token';
1203
+ getToken: () => string | Promise<string>;
1204
+ refreshToken?: () => string | Promise<string>;
1205
+ onUnauthorized?: (error: Error) => void;
1206
+ }
1207
+ | {
1208
+ strategy: 'custom';
1209
+ resolve: () => Record<string, unknown> | Promise<Record<string, unknown>>;
1210
+ };
1211
+
1212
+ export interface RealtimeReconnectOptions {
1213
+ enabled?: boolean;
1214
+ maxAttempts?: number;
1215
+ baseDelay?: number;
1216
+ maxDelay?: number;
1217
+ factor?: number;
1218
+ jitter?: boolean;
1219
+ }
1220
+
1221
+ export interface RealtimeOptions {
1222
+ adapter: RealtimeAdapter;
1223
+ auth?: RealtimeAuthStrategy;
1224
+ autoConnect?: boolean;
1225
+ reconnect?: RealtimeReconnectOptions;
1226
+ isBrowser?: boolean;
1227
+ }
1228
+
1229
+ export interface RealtimeClient {
1230
+ capabilities: Record<string, boolean>;
1231
+ isConnected(): boolean;
1232
+ connect(overrideAuth?: Record<string, unknown> | null): Promise<void>;
1233
+ disconnect(): void;
1234
+ reconnect(): Promise<void>;
1235
+ reauthenticate(): Promise<void>;
1236
+ subscribe(
1237
+ identifier: string,
1238
+ params?: Record<string, unknown>,
1239
+ callbacks?: RealtimeSubscriptionCallbacks
1240
+ ): RealtimeSubscription;
1241
+ on(event: 'connected' | 'disconnected' | 'reconnecting' | 'reconnected' | 'unauthorized' | 'error', handler: (...args: any[]) => void): () => void;
1242
+ once(event: string, handler: (...args: any[]) => void): () => void;
1243
+ off(event: string, handler: (...args: any[]) => void): () => void;
1244
+ destroy(): void;
1245
+ }
1246
+
1247
+ export function createRealtime(options: RealtimeOptions): RealtimeClient;
1248
+ export function realtime(options: RealtimeOptions): RealtimeClient & { plugin: WebspressoPlugin };
1249
+ export function realtimePlugin(options: RealtimeOptions): WebspressoPlugin;
1250
+
1251
+ export interface WebSocketAdapterOptions {
1252
+ url: string | ((context: { auth: Record<string, unknown> }) => string | Promise<string>);
1253
+ authTransport?: 'query' | 'header' | 'protocol' | 'message';
1254
+ queryParamName?: string;
1255
+ protocols?: string | string[];
1256
+ WebSocket?: any;
1257
+ messages?: {
1258
+ subscribe?: (args: { identifier: string; params: Record<string, unknown> }) => unknown;
1259
+ unsubscribe?: (args: { identifier: string; params: Record<string, unknown> }) => unknown;
1260
+ perform?: (args: { identifier: string; action: string; data?: unknown }) => unknown;
1261
+ send?: (args: { identifier: string; data: unknown }) => unknown;
1262
+ auth?: (args: { token: string; metadata: Record<string, unknown> }) => unknown;
1263
+ };
1264
+ capabilities?: Record<string, boolean>;
1265
+ }
1266
+
1267
+ export function websocket(options: WebSocketAdapterOptions): RealtimeAdapter;
1268
+ export function createWebSocketAdapter(options: WebSocketAdapterOptions): RealtimeAdapter;
1269
+
1270
+ export interface SseAdapterOptions {
1271
+ url: string | ((context: { auth: Record<string, unknown> }) => string | Promise<string>);
1272
+ queryParamName?: string;
1273
+ EventSource?: any;
1274
+ capabilities?: Record<string, boolean>;
1275
+ }
1276
+
1277
+ export function sse(options: SseAdapterOptions): RealtimeAdapter;
1278
+ export function createSseAdapter(options: SseAdapterOptions): RealtimeAdapter;
1279
+
1280
+ export interface SocketIoAdapterOptions {
1281
+ url?: string | ((context: { auth: Record<string, unknown> }) => string | Promise<string>);
1282
+ path?: string;
1283
+ io?: any;
1284
+ socketOptions?: Record<string, unknown>;
1285
+ capabilities?: Record<string, boolean>;
1286
+ }
1287
+
1288
+ export function socketIo(options: SocketIoAdapterOptions): RealtimeAdapter;
1289
+ export function createSocketIoAdapter(options: SocketIoAdapterOptions): RealtimeAdapter;
1290
+
1291
+ export interface RedisAdapterOptions {
1292
+ pubClient?: any;
1293
+ subClient?: any;
1294
+ redis?: any;
1295
+ channelPrefix?: string;
1296
+ nodeId?: string;
1297
+ serialize?: (envelope: unknown) => string;
1298
+ deserialize?: (raw: string) => unknown;
1299
+ inMemory?: boolean;
1300
+ capabilities?: Record<string, boolean>;
1301
+ }
1302
+
1303
+ export interface RedisRealtimeAdapter extends RealtimeAdapter {
1304
+ publish(identifier: string, data: unknown): Promise<void>;
1305
+ perform(identifier: string, action: string, data?: unknown): Promise<void>;
1306
+ readonly isInMemory: boolean;
1307
+ readonly nodeId: string;
1308
+ }
1309
+
1310
+ export function redis(options?: RedisAdapterOptions): RedisRealtimeAdapter;
1311
+ export function createRedisAdapter(options?: RedisAdapterOptions): RedisRealtimeAdapter;
1312
+
711
1313
  // --- Application kernel (use `kernel.createApp`; not the SSR `createApp`) ---
712
1314
 
713
1315
  export type KernelEventSource = 'orm' | 'auth' | 'route' | 'plugin' | 'system';
@@ -807,3 +1409,178 @@ export interface WebspressoKernel {
807
1409
  }
808
1410
 
809
1411
  export const kernel: WebspressoKernel;
1412
+
1413
+ // --- Background Job Queue ---
1414
+
1415
+ export interface JobJSON {
1416
+ id: string | number;
1417
+ name: string;
1418
+ data: unknown;
1419
+ status: 'pending' | 'running' | 'completed' | 'failed';
1420
+ attempts: number;
1421
+ maxAttempts: number;
1422
+ backoff: unknown;
1423
+ priority: number;
1424
+ timeout: number;
1425
+ progress: number;
1426
+ result: unknown;
1427
+ error: string | null;
1428
+ createdAt: string;
1429
+ updatedAt: string;
1430
+ runAt: string;
1431
+ }
1432
+
1433
+ export class Job {
1434
+ id: string | number;
1435
+ name: string;
1436
+ data: any;
1437
+ status: 'pending' | 'running' | 'completed' | 'failed';
1438
+ attempts: number;
1439
+ maxAttempts: number;
1440
+ backoff: number | { type?: 'exponential' | 'fixed'; delay?: number };
1441
+ priority: number;
1442
+ timeout: number;
1443
+ progressValue: number;
1444
+ result: any;
1445
+ error: any;
1446
+ createdAt: Date;
1447
+ updatedAt: Date;
1448
+ runAt: Date;
1449
+ constructor(options?: Record<string, any>);
1450
+ progress(percent: number, message?: string): void;
1451
+ getNextRetryDelay(): number;
1452
+ toJSON(): JobJSON;
1453
+ }
1454
+
1455
+ export interface QueueAdapter {
1456
+ enqueue(job: Job): Promise<Job>;
1457
+ dequeue(jobNames?: string[]): Promise<Job | null>;
1458
+ complete(jobId: string | number, result?: unknown): Promise<Job | null>;
1459
+ fail(jobId: string | number, error?: unknown): Promise<Job | null>;
1460
+ retry(jobId: string | number, delayMs: number, error?: unknown): Promise<Job | null>;
1461
+ getJob(jobId: string | number): Promise<Job | null>;
1462
+ getStats(): Promise<{ pending: number; running: number; completed: number; failed: number; total: number }>;
1463
+ clear(): Promise<void>;
1464
+ }
1465
+
1466
+ export class MemoryQueueAdapter implements QueueAdapter {
1467
+ enqueue(job: Job): Promise<Job>;
1468
+ dequeue(jobNames?: string[]): Promise<Job | null>;
1469
+ complete(jobId: string | number, result?: unknown): Promise<Job | null>;
1470
+ fail(jobId: string | number, error?: unknown): Promise<Job | null>;
1471
+ retry(jobId: string | number, delayMs: number, error?: unknown): Promise<Job | null>;
1472
+ getJob(jobId: string | number): Promise<Job | null>;
1473
+ getStats(): Promise<{ pending: number; running: number; completed: number; failed: number; total: number }>;
1474
+ clear(): Promise<void>;
1475
+ }
1476
+
1477
+ export class DatabaseQueueAdapter implements QueueAdapter {
1478
+ constructor(options: { knex: Knex; tableName?: string });
1479
+ ensureTable(): Promise<void>;
1480
+ enqueue(job: Job): Promise<Job>;
1481
+ dequeue(jobNames?: string[]): Promise<Job | null>;
1482
+ complete(jobId: string | number, result?: unknown): Promise<Job | null>;
1483
+ fail(jobId: string | number, error?: unknown): Promise<Job | null>;
1484
+ retry(jobId: string | number, delayMs: number, error?: unknown): Promise<Job | null>;
1485
+ getJob(jobId: string | number): Promise<Job | null>;
1486
+ getStats(): Promise<{ pending: number; running: number; completed: number; failed: number; total: number }>;
1487
+ clear(): Promise<void>;
1488
+ }
1489
+
1490
+ export class RedisQueueAdapter implements QueueAdapter {
1491
+ constructor(options: { client: unknown; prefix?: string });
1492
+ enqueue(job: Job): Promise<Job>;
1493
+ dequeue(jobNames?: string[]): Promise<Job | null>;
1494
+ complete(jobId: string | number, result?: unknown): Promise<Job | null>;
1495
+ fail(jobId: string | number, error?: unknown): Promise<Job | null>;
1496
+ retry(jobId: string | number, delayMs: number, error?: unknown): Promise<Job | null>;
1497
+ getJob(jobId: string | number): Promise<Job | null>;
1498
+ getStats(): Promise<{ pending: number; running: number; completed: number; failed: number; total: number }>;
1499
+ clear(): Promise<void>;
1500
+ }
1501
+
1502
+ export interface DispatchOptions {
1503
+ delay?: number | Date;
1504
+ attempts?: number;
1505
+ priority?: number;
1506
+ timeout?: number;
1507
+ backoff?: number | { type?: 'exponential' | 'fixed'; delay?: number };
1508
+ }
1509
+
1510
+ export class QueueManager extends import('events').EventEmitter {
1511
+ adapter: QueueAdapter;
1512
+ concurrency: number;
1513
+ pollInterval: number;
1514
+ isRunning: boolean;
1515
+ isPaused: boolean;
1516
+ isDraining: boolean;
1517
+ constructor(options?: {
1518
+ adapter?: QueueAdapter;
1519
+ concurrency?: number;
1520
+ pollInterval?: number;
1521
+ jobsDir?: string | null;
1522
+ autoStart?: boolean;
1523
+ });
1524
+ define(name: string, handler: (job: Job, ctx: { queue: QueueManager }) => Promise<any> | any, options?: DispatchOptions): this;
1525
+ has(name: string): boolean;
1526
+ dispatch(name: string, data?: unknown, options?: DispatchOptions): Promise<Job>;
1527
+ start(): this;
1528
+ pause(): this;
1529
+ resume(): this;
1530
+ drain(timeoutMs?: number): Promise<void>;
1531
+ stop(): Promise<void>;
1532
+ getStats(): Promise<Record<string, unknown>>;
1533
+ getJob(jobId: string | number): Promise<Job | null>;
1534
+ loadJobsFromDirectory(dirPath: string): void;
1535
+ }
1536
+
1537
+ export function createQueueManager(options?: {
1538
+ adapter?: QueueAdapter;
1539
+ concurrency?: number;
1540
+ pollInterval?: number;
1541
+ jobsDir?: string | null;
1542
+ autoStart?: boolean;
1543
+ }): QueueManager;
1544
+
1545
+ export function queuePlugin(options?: {
1546
+ adapter?: 'memory' | 'database' | 'db' | 'redis' | QueueAdapter;
1547
+ db?: { knex: Knex };
1548
+ knex?: Knex;
1549
+ redisClient?: unknown;
1550
+ tableName?: string;
1551
+ concurrency?: number;
1552
+ pollInterval?: number;
1553
+ jobsDir?: string;
1554
+ autoStart?: boolean;
1555
+ }): PluginDefinition;
1556
+
1557
+ // --- SSR Streaming & Chunked Transfer ---
1558
+
1559
+ export interface HtmlStreamOptions {
1560
+ env: import('nunjucks').Environment;
1561
+ templatePath: string;
1562
+ context?: Record<string, unknown>;
1563
+ defer?: Record<string, Promise<unknown> | { promise: Promise<unknown>; template?: string }>;
1564
+ }
1565
+
1566
+ export interface RenderStreamOptions {
1567
+ env?: import('nunjucks').Environment;
1568
+ defer?: Record<string, Promise<unknown> | { promise: Promise<unknown>; template?: string }>;
1569
+ status?: number;
1570
+ flushImmediately?: boolean;
1571
+ }
1572
+
1573
+ export function createHtmlStream(options: HtmlStreamOptions): import('stream').Readable;
1574
+ export function renderStream(
1575
+ res: import('express').Response,
1576
+ templatePath: string,
1577
+ context?: Record<string, unknown>,
1578
+ options?: RenderStreamOptions
1579
+ ): Promise<void>;
1580
+
1581
+ export const ssr: {
1582
+ createHtmlStream: typeof createHtmlStream;
1583
+ renderStream: typeof renderStream;
1584
+ };
1585
+
1586
+