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.
- package/README.md +358 -8
- package/bin/commands/service.js +193 -0
- package/bin/webspresso.js +2 -0
- package/core/auth/middleware.js +9 -0
- package/core/compression/compressible.js +51 -0
- package/core/compression/compression-stream.js +59 -0
- package/core/compression/index.js +25 -0
- package/core/compression/middleware.js +301 -0
- package/core/compression/negotiate.js +153 -0
- package/core/errors/base.js +31 -0
- package/core/errors/domain.js +193 -0
- package/core/errors/http.js +133 -0
- package/core/errors/index.js +77 -0
- package/core/errors/middleware.js +162 -0
- package/core/errors/normalize.js +171 -0
- package/core/orm/complexity.js +120 -0
- package/core/orm/events.js +64 -14
- package/core/orm/index.js +45 -10
- package/core/orm/model.js +2 -0
- package/core/orm/query-builder.js +30 -4
- package/core/orm/repository.js +57 -25
- package/core/orm/transaction.js +39 -5
- package/core/orm/utils.js +6 -3
- package/core/queue/adapters/database.js +284 -0
- package/core/queue/adapters/memory.js +155 -0
- package/core/queue/adapters/redis.js +336 -0
- package/core/queue/index.js +21 -0
- package/core/queue/job.js +103 -0
- package/core/queue/manager.js +387 -0
- package/core/realtime/adapters/redis.js +431 -0
- package/core/realtime/adapters/socket-io.js +203 -0
- package/core/realtime/adapters/sse.js +210 -0
- package/core/realtime/adapters/websocket.js +337 -0
- package/core/realtime/auth-manager.js +140 -0
- package/core/realtime/core.js +359 -0
- package/core/realtime/emitter.js +98 -0
- package/core/realtime/errors.js +71 -0
- package/core/realtime/index.js +79 -0
- package/core/realtime/reconnect-manager.js +118 -0
- package/core/realtime/registry.js +243 -0
- package/core/shutdown/http-adapter.js +134 -0
- package/core/shutdown/index.js +12 -0
- package/core/shutdown/shutdown-manager.js +312 -0
- package/core/ssr/index.js +13 -0
- package/core/ssr/stream.js +174 -0
- package/index.d.ts +829 -52
- package/index.js +103 -0
- package/package.json +22 -2
- package/plugins/admin-panel/api.js +37 -4
- package/plugins/admin-panel/app.js +57 -17
- package/plugins/admin-panel/client/parts/01-state-api-breadcrumb.js +4 -1
- package/plugins/admin-panel/client/parts/04-field-renderers.js +102 -58
- package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +37 -23
- package/plugins/admin-panel/client/parts/08-record-list.js +17 -24
- package/plugins/admin-panel/client/parts/09-record-form.js +168 -19
- package/plugins/admin-panel/core/admin-module.js +2 -2
- package/plugins/admin-panel/core/api-extensions.js +149 -0
- package/plugins/admin-panel/field-renderers/rich-text.js +3 -2
- package/plugins/admin-panel/index.js +90 -17
- package/plugins/admin-panel/modules/custom-pages.js +144 -45
- package/plugins/admin-panel/modules/menu.js +85 -0
- package/plugins/admin-panel/modules/user-management.js +5 -2
- package/plugins/admin-panel/vendor/mithril.min.js +1 -0
- package/plugins/admin-panel/vendor/quill.min.js +7 -0
- package/plugins/admin-panel/vendor/quill.snow.min.css +945 -0
- package/plugins/admin-panel/vendor/tailwind.min.js +65 -0
- package/plugins/basic-auth/index.js +293 -0
- package/plugins/data-exchange/export-xlsx.js +5 -1
- package/plugins/data-exchange/import.js +16 -2
- package/plugins/data-exchange/index.js +14 -0
- package/plugins/email/index.js +14 -1
- package/plugins/index.js +29 -0
- package/plugins/queue/index.js +108 -0
- package/plugins/realtime/index.js +150 -0
- package/plugins/recaptcha/index.js +11 -0
- package/plugins/redirect/index.js +9 -1
- package/plugins/rest-resources/index.js +280 -11
- package/plugins/upload/index.js +16 -0
- package/src/app-context.js +46 -6
- package/src/file-router.js +196 -21
- package/src/helpers.js +20 -2
- package/src/plugin-manager.js +124 -4
- package/src/server.js +304 -44
- package/src/services/builtins/auth.js +273 -0
- package/src/services/builtins/exchange.js +229 -0
- package/src/services/builtins/mail.js +134 -0
- package/src/services/builtins/media.js +196 -0
- package/src/services/builtins/system.js +221 -0
- package/src/services/discovery.js +140 -0
- package/src/services/executor.js +292 -0
- package/src/services/index.js +70 -0
- package/src/services/memoize.js +256 -0
- package/src/services/registry.js +288 -0
- package/src/services/validator.js +281 -0
package/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:
|
|
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(): {
|
|
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: {
|
|
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
|
|
126
|
-
|
|
127
|
-
):
|
|
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
|
-
|
|
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:
|
|
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
|
|
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:
|
|
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<
|
|
366
|
-
list(): Promise<
|
|
367
|
-
get(): Promise<
|
|
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<
|
|
380
|
-
findOne(conditions: Record<string, unknown>, options?: FindOptions): Promise<
|
|
381
|
-
findAll(options?: FindOptions): Promise<
|
|
382
|
-
create(data: Record<string, unknown>): Promise<
|
|
383
|
-
createMany(data: Record<string, unknown>[]): Promise<
|
|
384
|
-
update(id: string | number, data: Record<string, unknown>): Promise<
|
|
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<
|
|
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
|
|
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
|
|
487
|
-
query(modelName: string, scopeContext?: ScopeContext): QueryBuilder
|
|
488
|
-
transaction<T>(callback: (ctx: TransactionContext) => 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
|
+
|