@luckystack/core 0.1.0

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.
@@ -0,0 +1,1128 @@
1
+ import { a7 as ErrorParam, a8 as ErrorResponseInput, a9 as NormalizedErrorResponse, b as AuthProps, B as BaseSessionLayout } from './pageRouteValidation-CxZJVwZh.js';
2
+ export { A as ApiMethodMap, a as AppConfig, aa as AuthConfig, ab as CorsConfig, D as DEFAULT_PAGE_ROUTE_RULES, ac as DEFAULT_PROJECT_CONFIG, ad as DevConfig, ae as HttpConfig, H as HttpMethodLiteral, af as HttpStreamConfig, ag as INVALID_ERROR_RESPONSE_CODE, L as Logger, c as LoggerContext, d as LoggingConfig, N as Notifier, e as NotifyInput, f as NotifyParam, ah as OfflineQueueConfig, P as PagePathValidationResult, g as PageRouteRules, ai as PasswordPolicyConfig, aj as PathsConfig, h as ProjectConfig, ak as ProjectConfigInput, R as RateLimitingConfig, S as SOCKETSTATUS, al as SecurityHeadersConfig, am as ServiceRouteParseError, an as ServiceRouteParseResult, ao as ServiceRouteParseSuccess, i as SessionConfig, j as SessionLocation, ap as SocketConfig, aq as SyncConfig, ar as SyncStreamThrottleConfig, as as TransportRouteParseError, at as TransportRouteParseResult, au as TransportRouteParseSuccess, k as buildApiResponseEventName, l as buildApiStreamEventName, m as buildGetJoinedRoomsResponseEventName, n as buildJoinRoomResponseEventName, o as buildLeaveRoomResponseEventName, p as buildSyncProgressEventName, q as buildSyncResponseEventName, r as clearCsrfToken, av as createDevLogger, aw as defaultHttpStatusForResponse, s as enqueueApiRequest, t as enqueueSyncRequest, u as flushApiQueue, v as flushSyncQueue, w as getApiQueueSize, x as getCsrfToken, y as getLogger, z as getNotifier, C as getProjectConfig, ax as getProjectName, E as getRegisteredApiMethod, F as getSyncQueueSize, G as httpFetch, I as incrementResponseIndex, J as isApiMethodMapRegistered, ay as isErrorParamArray, az as isLoggerRegistered, K as isOnline, M as isProjectConfigRegistered, O as normalizeErrorResponseCore, Q as notify, T as parseServiceRouteName, aA as parseTransportRouteName, U as registerApiMethodMap, V as registerLogger, W as registerNotifier, X as registerProjectConfig, Y as removeApiQueueItem, Z as removeApiQueueItemsByKey, _ as removeSyncQueueItem, aB as resetLoggerForTests, $ as setSocket, a0 as sleep, a1 as socket, a2 as socketEventNames, a3 as statusContent, a4 as tryCatch, a5 as validatePagePath, a6 as waitForSocket } from './pageRouteValidation-CxZJVwZh.js';
3
+ export { ApiTypeMap, StreamPayload, SyncTypeMap } from './apiTypeStubs.js';
4
+ import { Server, Socket } from 'socket.io';
5
+ import { z } from 'zod';
6
+ import * as _prisma_client_runtime_library from '@prisma/client/runtime/library';
7
+ import * as _prisma_client from '@prisma/client';
8
+ import { PrismaClient } from '@prisma/client';
9
+ import Redis, { Redis as Redis$1 } from 'ioredis';
10
+ import { ServerResponse, IncomingMessage } from 'node:http';
11
+ import 'socket.io-client';
12
+
13
+ type LanguageCode = string;
14
+ interface TranslateInput {
15
+ language: LanguageCode;
16
+ key: string;
17
+ params?: ErrorParam[];
18
+ }
19
+ type TranslateFunction = (input: TranslateInput) => string;
20
+ interface NormalizeErrorResponseInput {
21
+ response: ErrorResponseInput;
22
+ preferredLocale?: string | null;
23
+ userLanguage?: string | null;
24
+ fallbackHttpStatus?: number;
25
+ }
26
+ interface LocalizedNormalizer {
27
+ normalizeErrorResponse: (input: NormalizeErrorResponseInput) => NormalizedErrorResponse;
28
+ resolveErrorMessage: (input: {
29
+ errorCode: string;
30
+ errorParams?: ErrorParam[];
31
+ preferredLocale?: string | null;
32
+ userLanguage?: string | null;
33
+ }) => string;
34
+ extractLanguageFromHeader: (header?: string | string[]) => LanguageCode | null;
35
+ }
36
+ interface CreateLocalizedNormalizerInput {
37
+ translate: TranslateFunction;
38
+ /**
39
+ * Language the framework falls back to when the caller provides no
40
+ * language hints. Defaults to 'en'.
41
+ */
42
+ defaultLanguage?: LanguageCode;
43
+ /**
44
+ * Decide whether a given string is an acceptable language code. Defaults
45
+ * to accepting any non-empty short-form code (ISO 639-1 style). Projects
46
+ * that only support a closed set should pass a narrower predicate.
47
+ */
48
+ isSupportedLanguage?: (code: string) => boolean;
49
+ }
50
+ declare const createLocalizedNormalizer: (input: CreateLocalizedNormalizerInput) => LocalizedNormalizer;
51
+ declare const registerLocalizedNormalizer: (normalizer: LocalizedNormalizer) => void;
52
+ declare const getLocalizedNormalizer: () => LocalizedNormalizer;
53
+ declare const isLocalizedNormalizerRegistered: () => boolean;
54
+ declare const normalizeErrorResponse: (input: NormalizeErrorResponseInput) => NormalizedErrorResponse;
55
+ declare const resolveErrorMessage: (input: {
56
+ errorCode: string;
57
+ errorParams?: ErrorParam[];
58
+ preferredLocale?: string | null;
59
+ userLanguage?: string | null;
60
+ }) => string;
61
+ declare const extractLanguageFromHeader: (header?: string | string[]) => LanguageCode | null;
62
+
63
+ interface ErrorFormatterContext {
64
+ /** Resolved route name (`api/billing/getInvoice/v1`, `sync/...`). */
65
+ routeName: string;
66
+ /** Transport that triggered the error. */
67
+ transport: 'socket' | 'http';
68
+ /** Session id when available. */
69
+ userId?: string | null;
70
+ }
71
+ type ErrorFormatter = (error: {
72
+ status: 'error';
73
+ errorCode?: string;
74
+ message?: string;
75
+ httpStatus?: number;
76
+ [key: string]: unknown;
77
+ }, ctx: ErrorFormatterContext) => Record<string, unknown>;
78
+ /**
79
+ * Register a global error-response formatter. Receives the normalized
80
+ * error envelope + context; return a (possibly extended) object that the
81
+ * framework will emit. Pass `null` to unregister.
82
+ *
83
+ * For per-endpoint overrides, export `errorFormatter` from your
84
+ * `_api/foo_v1.ts` file directly — the api/sync handler imports + invokes
85
+ * it through `applyErrorFormatter`.
86
+ */
87
+ declare const registerErrorFormatter: (formatter: ErrorFormatter | null) => void;
88
+ /** Read the active global formatter (or null). */
89
+ declare const getErrorFormatter: () => ErrorFormatter | null;
90
+ /**
91
+ * Apply per-route → global → identity formatter chain to a normalized
92
+ * error envelope. Returns the envelope unchanged when `status !== 'error'`
93
+ * so it's safe to wrap every emit call. Errors thrown inside a formatter
94
+ * are caught and logged; the un-formatted envelope is returned in that
95
+ * case to keep the error path crash-resistant.
96
+ */
97
+ declare const applyErrorFormatter: (input: {
98
+ response: Record<string, unknown> & {
99
+ status?: string;
100
+ };
101
+ routeName: string;
102
+ transport: "socket" | "http";
103
+ userId?: string | null;
104
+ perRouteFormatter?: ErrorFormatter | undefined | null;
105
+ }) => Record<string, unknown>;
106
+
107
+ interface apiMessage {
108
+ name: string;
109
+ data: object;
110
+ responseIndex: number;
111
+ }
112
+ interface syncMessage {
113
+ name: string;
114
+ data: object;
115
+ cb: string;
116
+ receiver: string;
117
+ responseIndex?: number;
118
+ ignoreSelf?: boolean;
119
+ }
120
+ declare const setIoInstance: (io: Server | null) => void;
121
+ declare const getIoInstance: () => Server | null;
122
+
123
+ type RuntimeMapRecord = Record<string, unknown>;
124
+ interface RuntimeApiMapsResult {
125
+ apisObject: RuntimeMapRecord;
126
+ functionsObject: RuntimeMapRecord;
127
+ }
128
+ interface RuntimeSyncMapsResult {
129
+ syncObject: RuntimeMapRecord;
130
+ functionsObject: RuntimeMapRecord;
131
+ }
132
+ interface RuntimeMapsProvider {
133
+ getRuntimeApiMaps: () => Promise<RuntimeApiMapsResult>;
134
+ getRuntimeSyncMaps: () => Promise<RuntimeSyncMapsResult>;
135
+ }
136
+ declare const registerRuntimeMapsProvider: (provider: RuntimeMapsProvider) => void;
137
+ declare const isRuntimeMapsProviderRegistered: () => boolean;
138
+ declare const getRuntimeApiMaps: () => Promise<RuntimeApiMapsResult>;
139
+ declare const getRuntimeSyncMaps: () => Promise<RuntimeSyncMapsResult>;
140
+
141
+ interface EmailMessage {
142
+ to: string | string[];
143
+ subject: string;
144
+ html: string;
145
+ text?: string;
146
+ from?: string;
147
+ replyTo?: string;
148
+ cc?: string | string[];
149
+ bcc?: string | string[];
150
+ }
151
+ type EmailResult = {
152
+ ok: true;
153
+ id: string;
154
+ } | {
155
+ ok: false;
156
+ reason: string;
157
+ cause?: unknown;
158
+ };
159
+ interface EmailSender {
160
+ /** Adapter identifier for logs + diagnostics ("console", "resend", "smtp", etc.). */
161
+ name: string;
162
+ send: (message: EmailMessage) => Promise<EmailResult>;
163
+ }
164
+ /**
165
+ * Registry keyed by adapter-slot name. Reserved slots used by the
166
+ * framework:
167
+ *
168
+ * - `'default'` — fallback when no specific slot matches.
169
+ * - `'transactional'` — login password-reset, account-confirmation,
170
+ * invoice-receipt, security-critical emails.
171
+ * - `'marketing'` — newsletters, announcements; consumers MAY route
172
+ * bulk sends here.
173
+ * - `'diagnostics'` — diagnostic-test endpoints (e.g. playground).
174
+ *
175
+ * Custom slot names ('billing', 'support', etc.) are allowed; consumers
176
+ * resolve them explicitly via `sendEmail({ adapter: 'billing', ... })`.
177
+ */
178
+ type EmailSenderRegistry = Partial<Record<string, EmailSender>>;
179
+ /**
180
+ * Register a single global email sender. Kept for backwards compatibility.
181
+ * For new code, prefer `registerEmailSenders({ ... })` which lets the
182
+ * framework route transactional vs. marketing through different adapters.
183
+ */
184
+ declare const registerEmailSender: (sender: EmailSender) => void;
185
+ /**
186
+ * Register multiple named email senders. Replaces the entire registry
187
+ * (last-write-wins). Use this when your app routes different message
188
+ * types through different adapters — e.g. Resend for marketing and SMTP
189
+ * for transactional. The framework picks adapter slots by convention
190
+ * (see `EmailSenderRegistry`); consumer code can pick explicitly via
191
+ * `sendEmail({ adapter: 'foo', ... })`.
192
+ */
193
+ declare const registerEmailSenders: (senders: EmailSenderRegistry) => void;
194
+ /** Read the legacy single sender (or the `default` slot of the registry). */
195
+ declare const getEmailSender: () => EmailSender | null;
196
+ /**
197
+ * Read a specific named sender. Falls back to the legacy single sender
198
+ * when the requested slot is absent. Returns null when neither exists.
199
+ * Pass the convention-based slots (`'transactional'`, `'marketing'`,
200
+ * `'diagnostics'`, `'default'`) or any custom slot you registered.
201
+ */
202
+ declare const getEmailSenderByName: (name: string) => EmailSender | null;
203
+ /** Read every registered slot name. */
204
+ declare const listEmailSenderNames: () => string[];
205
+ declare const isEmailSenderRegistered: () => boolean;
206
+
207
+ interface DeployResourceShape {
208
+ type: 'redis' | 'mongo';
209
+ urlEnvKey: string;
210
+ synchronizedEnvKeys?: string[];
211
+ }
212
+ interface DeployEnvironmentShape {
213
+ redis: string;
214
+ mongo: string;
215
+ fallback?: string;
216
+ bindings: Record<string, string>;
217
+ }
218
+ interface DeployRoutingShape {
219
+ onMissingService?: 'hard-error' | 'proxy-fallback';
220
+ missingServiceErrorCode?: string;
221
+ enableUnhealthyFallback?: boolean;
222
+ strictBootHandshake?: boolean;
223
+ /** Default port the router listens on when ROUTER_PORT env var is unset (default 4000). */
224
+ defaultRouterPort?: number;
225
+ /** Default ms between health polls when not specified per-call (default 5000). */
226
+ defaultHealthPollMs?: number;
227
+ /** ms before the fallback `/_health` probe times out (default 3000). */
228
+ healthProbeTimeoutMs?: number;
229
+ /** Seconds the boot-UUID Redis key lives for (default 3600 = 1h). */
230
+ bootKeyTtlSeconds?: number;
231
+ }
232
+ interface DeployDevelopmentShape {
233
+ enableFallbackRouting?: boolean;
234
+ healthPollMs?: number;
235
+ switchNewTrafficToLocalWhenHealthy?: boolean;
236
+ }
237
+ interface DeployConfigShape {
238
+ resources: Record<string, DeployResourceShape>;
239
+ environments?: Record<string, DeployEnvironmentShape>;
240
+ routing?: DeployRoutingShape;
241
+ development?: DeployDevelopmentShape;
242
+ }
243
+ declare const registerDeployConfig: (config: DeployConfigShape) => DeployConfigShape;
244
+ declare const getDeployConfig: () => DeployConfigShape;
245
+ declare const isDeployConfigRegistered: () => boolean;
246
+
247
+ interface ServiceDefinition {
248
+ /** 'root' -> reserved for the system service. Otherwise a folder name under srcDir. */
249
+ source: 'root' | string;
250
+ }
251
+ interface PresetDefinition {
252
+ description?: string;
253
+ /** Services that are bundled together into one backend artifact. */
254
+ services: string[];
255
+ }
256
+ interface ServicesConfigShape {
257
+ services: Record<string, ServiceDefinition>;
258
+ presets: Record<string, PresetDefinition>;
259
+ }
260
+ declare const registerServicesConfig: (config: ServicesConfigShape) => ServicesConfigShape;
261
+ declare const getServicesConfig: () => ServicesConfigShape;
262
+ declare const isServicesConfigRegistered: () => boolean;
263
+
264
+ type LocaleReloader = () => void | Promise<void>;
265
+ declare const registerLocaleReloader: (reloader: LocaleReloader) => LocaleReloader;
266
+ declare const getLocaleReloader: () => LocaleReloader | null;
267
+
268
+ interface SentryInstance {
269
+ captureException: (exception: unknown, ...args: unknown[]) => string;
270
+ captureMessage: (message: string, ...args: unknown[]) => string;
271
+ setUser: (user: unknown) => void;
272
+ setContext: (key: string, context: unknown) => void;
273
+ startInactiveSpan?: (context: unknown) => unknown;
274
+ }
275
+ declare const initSharedSentry: (instance: SentryInstance) => void;
276
+ declare const captureException: (error: unknown, context?: Record<string, unknown>) => void;
277
+ declare const captureMessage: (message: string, level?: "info" | "warning" | "error" | "fatal", context?: Record<string, unknown>) => void;
278
+ declare const setSentryUser: (user: {
279
+ id?: string;
280
+ email?: string;
281
+ username?: string;
282
+ } | null) => void;
283
+ declare const startSpan: (name: string, op: string) => unknown;
284
+
285
+ type ErrorTrackerContext = Record<string, unknown>;
286
+ interface ErrorTrackerUser {
287
+ id?: string;
288
+ email?: string;
289
+ username?: string;
290
+ [key: string]: unknown;
291
+ }
292
+ interface ErrorTrackerEvent {
293
+ /** When false, the adapter must not forward this event (beforeSend opt-out). */
294
+ forwarded: boolean;
295
+ kind: 'exception' | 'message';
296
+ payload: Record<string, unknown>;
297
+ }
298
+ type SpanResult<T> = T;
299
+ interface ErrorTracker {
300
+ /** Human-readable identifier (logs, diagnostics). */
301
+ name: string;
302
+ captureException: (error: unknown, context?: ErrorTrackerContext) => void;
303
+ captureMessage: (message: string, level: 'info' | 'warning' | 'error' | 'fatal', context?: ErrorTrackerContext) => void;
304
+ setUser: (user: ErrorTrackerUser | null) => void;
305
+ setContext?: (key: string, context: ErrorTrackerContext | null) => void;
306
+ startSpan?: <T>(name: string, op: string, fn: () => T) => T;
307
+ recordMetric?: (name: string, value: number, tags?: Record<string, string>) => void;
308
+ beforeSend?: (event: ErrorTrackerEvent) => ErrorTrackerEvent | null;
309
+ }
310
+ declare const registerErrorTracker: (tracker: ErrorTracker) => void;
311
+ declare const registerErrorTrackers: (trackers: ErrorTracker[]) => void;
312
+ declare const getActiveErrorTrackers: () => ErrorTracker[];
313
+ declare const captureExceptionAcrossTrackers: (error: unknown, context?: ErrorTrackerContext) => void;
314
+ declare const captureMessageAcrossTrackers: (message: string, level: "info" | "warning" | "error" | "fatal", context?: ErrorTrackerContext) => void;
315
+ declare const setErrorTrackerUser: (user: ErrorTrackerUser | null) => void;
316
+ declare const recordMetricAcrossTrackers: (name: string, value: number, tags?: Record<string, string>) => void;
317
+ declare const startSpanAcrossTrackers: <T>(name: string, op: string, fn: () => T) => T;
318
+
319
+ declare const EnvSchema: z.ZodObject<{
320
+ NODE_ENV: z.ZodDefault<z.ZodEnum<{
321
+ development: "development";
322
+ production: "production";
323
+ test: "test";
324
+ }>>;
325
+ SERVER_IP: z.ZodDefault<z.ZodString>;
326
+ SERVER_PORT: z.ZodDefault<z.ZodString>;
327
+ SECURE: z.ZodDefault<z.ZodEnum<{
328
+ true: "true";
329
+ false: "false";
330
+ }>>;
331
+ DNS: z.ZodDefault<z.ZodString>;
332
+ REDIS_HOST: z.ZodDefault<z.ZodString>;
333
+ REDIS_PORT: z.ZodDefault<z.ZodString>;
334
+ PROJECT_NAME: z.ZodDefault<z.ZodString>;
335
+ }, z.core.$loose>;
336
+ type RuntimeEnv = z.infer<typeof EnvSchema>;
337
+ declare const DEFAULT_ENV_FILES: string[];
338
+ declare const getEnvFiles: () => string[];
339
+ declare const loadEnvFiles: () => void;
340
+ declare const bootstrapEnv: () => RuntimeEnv;
341
+ declare const env: {
342
+ [x: string]: unknown;
343
+ NODE_ENV: "development" | "production" | "test";
344
+ SERVER_IP: string;
345
+ SERVER_PORT: string;
346
+ SECURE: "true" | "false";
347
+ DNS: string;
348
+ REDIS_HOST: string;
349
+ REDIS_PORT: string;
350
+ PROJECT_NAME: string;
351
+ };
352
+ declare const isProduction: boolean;
353
+
354
+ declare global {
355
+ var __luckystackPrisma: PrismaClient | undefined;
356
+ }
357
+ declare const prisma: PrismaClient<_prisma_client.Prisma.PrismaClientOptions, never, _prisma_client_runtime_library.DefaultArgs>;
358
+
359
+ interface RedisConnectionOptions {
360
+ host: string;
361
+ port: number;
362
+ username?: string;
363
+ password?: string;
364
+ }
365
+ declare const getRedisConnectionOptions: () => RedisConnectionOptions;
366
+ declare const redis: Redis;
367
+
368
+ type RedisKeyFormatter = (namespace: string, suffix: string) => string;
369
+ declare const defaultRedisKeyFormatter: RedisKeyFormatter;
370
+ declare const registerRedisKeyFormatter: (fn: RedisKeyFormatter) => void;
371
+ declare const getRedisKeyFormatter: () => RedisKeyFormatter;
372
+ declare const resetRedisKeyFormatterForTests: () => void;
373
+ declare const formatKey: (namespace: string, suffix?: string) => string;
374
+ declare const applyStrayKeyPrefix: (key: string) => string;
375
+
376
+ /**
377
+ * Acquire an exclusive lease named `name` for `ttlMs` milliseconds. Returns an
378
+ * opaque owner token on success, or `null` when another owner already holds it.
379
+ * `SET key token PX ttl NX` is atomic, so concurrent acquirers race safely.
380
+ * Keep the returned token to `renewLease` / `releaseLease`.
381
+ */
382
+ declare const acquireLease: (name: string, ttlMs: number) => Promise<string | null>;
383
+ /**
384
+ * Extend a held lease by `ttlMs` (sliding ownership). Returns true when the
385
+ * supplied `token` still owns the lease and the TTL was applied, false
386
+ * otherwise (lost ownership, expired, or Redis error).
387
+ */
388
+ declare const renewLease: (name: string, token: string, ttlMs: number) => Promise<boolean>;
389
+ /**
390
+ * Release a held lease. Owner-checked: only succeeds (returns true) when
391
+ * `token` is the current holder. Returns false on mismatch or Redis error.
392
+ */
393
+ declare const releaseLease: (name: string, token: string) => Promise<boolean>;
394
+
395
+ declare const DEFAULT_CLIENT_KEY = "default";
396
+ declare const registerPrismaClient: (client: PrismaClient, key?: string) => PrismaClient;
397
+ declare const registerRedisClient: (client: Redis$1, key?: string) => Redis$1;
398
+ declare const isPrismaClientRegistered: () => boolean;
399
+ declare const isRedisClientRegistered: () => boolean;
400
+ declare const getPrismaClientKeys: () => string[];
401
+ declare const getRedisClientKeys: () => string[];
402
+ declare const getPrismaClientFor: (key?: string) => PrismaClient;
403
+ declare const getRedisClientFor: (key?: string) => Redis$1;
404
+ declare const getPrismaClient: () => PrismaClient;
405
+ declare const getRedisClient: () => Redis$1;
406
+ declare const resetClientsForTests: () => void;
407
+
408
+ declare const attachSocketRedisAdapter: (io: Server) => void;
409
+
410
+ declare const BOOT_KEY_PREFIX = "luckystack:boot:";
411
+ declare const resolveEnvKey: () => string;
412
+ declare const writeBootUuid: (envKey?: string) => Promise<string>;
413
+ declare const readBootUuid: (envKey?: string) => Promise<string | null>;
414
+
415
+ declare const collectSynchronizedEnvKeys: () => string[];
416
+ declare const computeSynchronizedEnvHashes: () => Record<string, string | null>;
417
+ declare const hashSynchronizedValue: (value: string) => string;
418
+
419
+ declare const initConsolelog: () => void;
420
+
421
+ declare const registerRedactedLogKeys: (keys: readonly string[]) => void;
422
+ declare const getRedactedLogKeys: () => readonly string[];
423
+ declare const isRedactedLogKey: (key: string) => boolean;
424
+ declare const resetRedactedLogKeysForTests: () => void;
425
+
426
+ declare const getCookieValue: (cookieHeader: string | undefined, cookieName: string) => string | null;
427
+ declare const hasCookie: (cookieHeader: string | undefined, cookieName: string) => boolean;
428
+
429
+ /**
430
+ * HTTP API Utilities
431
+ *
432
+ * Helpers for HTTP API request handling including:
433
+ * - Smart HTTP method detection based on API name
434
+ * - Method validation
435
+ */
436
+ type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
437
+ /**
438
+ * Infer the HTTP method from an API name based on common naming conventions.
439
+ *
440
+ * - Names starting with "get" → GET
441
+ * - Names starting with "delete" or "remove" → DELETE
442
+ * - Names starting with "update" or "edit" → PUT
443
+ * - Everything else → POST (create operations, actions)
444
+ *
445
+ * @example
446
+ * ```typescript
447
+ * inferHttpMethod('getUserData') // → 'GET'
448
+ * inferHttpMethod('deleteAccount') // → 'DELETE'
449
+ * inferHttpMethod('updateProfile') // → 'PUT'
450
+ * inferHttpMethod('createUser') // → 'POST'
451
+ * inferHttpMethod('sendEmail') // → 'POST'
452
+ * ```
453
+ */
454
+ declare const inferHttpMethod: (apiName: string) => HttpMethod;
455
+ /**
456
+ * Get the effective HTTP method for an API.
457
+ * Uses explicit export if available, otherwise infers from name.
458
+ */
459
+ declare const getEffectiveHttpMethod: (apiConfig: {
460
+ httpMethod?: HttpMethod;
461
+ }, apiName: string) => HttpMethod;
462
+ /**
463
+ * Check if an HTTP method is valid for an API.
464
+ *
465
+ * @param requestMethod - The method from the HTTP request
466
+ * @param allowedMethod - The method the API accepts
467
+ * @returns true if the method matches or if it's a preflight OPTIONS request
468
+ */
469
+ declare const isMethodAllowed: (requestMethod: string, allowedMethod: HttpMethod) => boolean;
470
+
471
+ declare const ROOT_DIR: string;
472
+ declare const getSrcDir: () => string;
473
+ declare const getServerDir: () => string;
474
+ declare const getSharedDir: () => string;
475
+ declare const getUploadsDir: () => string;
476
+ declare const getPublicDir: () => string;
477
+ declare const getServerFunctionDirs: () => string[];
478
+ /**
479
+ * @deprecated Use `getServerFunctionDirs()` instead. Returns the first
480
+ * directory from the array, or `'functions'` resolved against the workspace
481
+ * root when no directories are configured.
482
+ */
483
+ declare const getServerFunctionsDir: () => string;
484
+ declare const getGeneratedSocketTypesPath: () => string;
485
+ declare const getGeneratedApiSchemasPath: () => string;
486
+ declare const getGeneratedApiDocsPath: () => string;
487
+ declare const SRC_DIR: string;
488
+ /** @deprecated use `getServerDir()` instead. */
489
+ declare const SERVER_DIR: string;
490
+ /** @deprecated use `getSharedDir()` instead. */
491
+ declare const SHARED_DIR: string;
492
+ /** @deprecated use `getUploadsDir()` instead. */
493
+ declare const UPLOADS_DIR: string;
494
+ /** @deprecated use `getPublicDir()` instead. */
495
+ declare const PUBLIC_DIR: string;
496
+ /** @deprecated use `getServerFunctionsDir()` instead. */
497
+ declare const SERVER_FUNCTIONS_DIR: string;
498
+ /** @deprecated use `getGeneratedSocketTypesPath()` instead. */
499
+ declare const GENERATED_SOCKET_TYPES_PATH: string;
500
+ /** @deprecated use `getGeneratedApiSchemasPath()` instead. */
501
+ declare const GENERATED_API_SCHEMAS_PATH: string;
502
+ /** @deprecated use `getGeneratedApiDocsPath()` instead. */
503
+ declare const GENERATED_API_DOCS_PATH: string;
504
+ declare const TSCONFIG_ALIAS_FILES: string[];
505
+ declare const resolveFromRoot: (...segments: string[]) => string;
506
+
507
+ type SocketMiddleware = (socket: Socket, next: (err?: Error) => void) => void;
508
+ /**
509
+ * Register a Socket.io middleware that will be wired via `io.use(...)` when
510
+ * the framework boots the socket server. Multiple middlewares can be
511
+ * registered — they run in registration order before any `connect`
512
+ * handler fires.
513
+ */
514
+ declare const registerSocketMiddleware: (mw: SocketMiddleware) => void;
515
+ /** Read the registered middleware list (read-only view). */
516
+ declare const getSocketMiddlewares: () => readonly SocketMiddleware[];
517
+ /** Test-only / hot-reload helper. Drops every registered middleware. */
518
+ declare const clearSocketMiddlewares: () => void;
519
+ /**
520
+ * Wire every registered middleware into the running Socket.io server.
521
+ * Called by `@luckystack/server`'s `loadSocket` after the `SocketIOServer`
522
+ * is constructed and before any `connect` handler is attached, so custom
523
+ * middlewares run before framework handlers see the socket.
524
+ */
525
+ declare const applySocketMiddlewares: (io: Server) => void;
526
+
527
+ interface CsrfCookieOptions {
528
+ sameSite?: 'strict' | 'lax' | 'none';
529
+ secure?: boolean;
530
+ /**
531
+ * MUST be false for CSRF cookies — the client needs to read the value
532
+ * with JS to attach it as a request header. The framework reads the
533
+ * value from the session record (Redis-side) rather than the cookie,
534
+ * so this flag is documentation/intent only.
535
+ */
536
+ httpOnly?: boolean;
537
+ path?: string;
538
+ maxAgeMs?: number;
539
+ }
540
+ interface CsrfConfig {
541
+ /** Cookie name used to deliver the CSRF token to the browser. */
542
+ cookieName: string;
543
+ /** Request-header name the client attaches on state-changing requests. */
544
+ headerName: string;
545
+ /** Length in bytes of the random token minted by the session layer. */
546
+ tokenLength: number;
547
+ cookieOptions: CsrfCookieOptions;
548
+ }
549
+ declare const DEFAULT_CSRF_CONFIG: CsrfConfig;
550
+ /**
551
+ * Override one or more CSRF settings. Merges shallowly with the current
552
+ * config; `cookieOptions` is deep-merged so a partial override of (say)
553
+ * `sameSite` does not clobber `path` / `maxAgeMs`. Last-write-wins.
554
+ */
555
+ declare const registerCsrfConfig: (input: Partial<CsrfConfig>) => void;
556
+ /** Read the active config at call time (never at module load). */
557
+ declare const getCsrfConfig: () => CsrfConfig;
558
+ /** Test-only — restore defaults between scenarios. */
559
+ declare const resetCsrfConfigForTests: () => void;
560
+
561
+ declare const serveAvatar: ({ routePath, res, }: {
562
+ routePath: string;
563
+ res: ServerResponse;
564
+ }) => Promise<void>;
565
+
566
+ interface ProcessUploadInput {
567
+ /** User the upload is associated with. */
568
+ userId: string;
569
+ /** Content type from the source (e.g. parsed from a data URL prefix). */
570
+ contentType: string;
571
+ /** Raw upload bytes — used for `sizeBytes` and passed to the encoder. */
572
+ buffer: Buffer;
573
+ /** Identifier for the upload category. Defaults to `'avatar'`. */
574
+ uploadKind?: string;
575
+ /** Final file name written to disk (used in the `onUploadComplete` payload). */
576
+ fileName: string;
577
+ /**
578
+ * Project-supplied encode + write step. Receives the raw buffer, does
579
+ * whatever encoding/resizing the installer wants (sharp, ffmpeg, raw fs
580
+ * write, S3 upload, etc.), and returns the final byte size on disk —
581
+ * pass through `buffer.byteLength` if you don't have a meaningful
582
+ * post-encode size.
583
+ */
584
+ encodeAndSave: (buffer: Buffer) => Promise<number>;
585
+ }
586
+ type ProcessUploadResult = {
587
+ status: 'success';
588
+ sizeBytes: number;
589
+ } | {
590
+ status: 'rejected';
591
+ errorCode: string;
592
+ } | {
593
+ status: 'error';
594
+ reason: string;
595
+ cause?: unknown;
596
+ };
597
+ /**
598
+ * Run a project-side upload through the framework's hook contract.
599
+ *
600
+ * Order of operations:
601
+ * 1. Dispatch `onUploadStart` — handlers may return a stop signal to reject
602
+ * the upload (content moderation, virus scanner, quota check).
603
+ * 2. Run the project-supplied `encodeAndSave` callback (wrapped in tryCatch).
604
+ * 3. Dispatch `onUploadComplete` with the final `sizeBytes`.
605
+ *
606
+ * Returns one of three statuses so the caller can map to its own response
607
+ * envelope without inspecting hook internals.
608
+ */
609
+ declare const processUpload: (input: ProcessUploadInput) => Promise<ProcessUploadResult>;
610
+
611
+ interface BindAddress {
612
+ ip: string;
613
+ port: number;
614
+ }
615
+ declare const registerBindAddress: (address: BindAddress) => void;
616
+ declare const getBindAddress: () => {
617
+ ip: string;
618
+ port: string;
619
+ };
620
+
621
+ interface AvatarConfig {
622
+ /**
623
+ * File format(s) the framework will look for on disk, in order. The first
624
+ * one that exists wins. Each entry is `{ extension, contentType }` so the
625
+ * `Content-Type` response header matches what the file actually is.
626
+ * Default: `[{ extension: 'webp', contentType: 'image/webp' }]`.
627
+ */
628
+ formats: {
629
+ extension: string;
630
+ contentType: string;
631
+ }[];
632
+ /**
633
+ * `Cache-Control` value sent with the avatar response.
634
+ * Default: `'public, max-age=86400'` (24 hours).
635
+ */
636
+ cacheControl: string;
637
+ }
638
+ declare const DEFAULT_AVATAR_CONFIG: AvatarConfig;
639
+ type DeepPartial<T> = {
640
+ [K in keyof T]?: T[K] extends object | undefined ? DeepPartial<NonNullable<T[K]>> : T[K];
641
+ };
642
+ type AvatarConfigInput = DeepPartial<AvatarConfig>;
643
+ declare const registerAvatarConfig: (config: AvatarConfigInput) => void;
644
+ declare const getAvatarConfig: () => AvatarConfig;
645
+
646
+ interface getParamsType {
647
+ method: string;
648
+ req: IncomingMessage;
649
+ res: ServerResponse;
650
+ queryString?: string;
651
+ }
652
+ declare function getParams({ method, req, res: _res, queryString }: getParamsType): Promise<Record<string, unknown> | null>;
653
+
654
+ /**
655
+ * Extract the authentication token from a Socket.io connection.
656
+ *
657
+ * Checks `config.sessionBasedToken` to determine where to look:
658
+ * - `true`: Token is in `socket.handshake.auth.token` (sessionStorage on client)
659
+ * - `false`: Token is in cookies via `socket.handshake.headers.cookie`
660
+ *
661
+ * @param socket - The Socket.io socket instance
662
+ * @returns The token string or null if not found
663
+ */
664
+ declare const extractTokenFromSocket: (socket: Pick<Socket, "handshake">) => string | null;
665
+
666
+ /**
667
+ * Extract the authentication token from an HTTP request.
668
+ *
669
+ * Supports tokens from:
670
+ * - `Authorization: Bearer <token>` header
671
+ * - the configured session cookie
672
+ *
673
+ * @param req - The HTTP incoming message
674
+ * @returns The token string or null if not found
675
+ */
676
+ declare const extractTokenFromRequest: (req: IncomingMessage) => string | null;
677
+
678
+ /**
679
+ * Shared validation utilities for API and Sync request handlers.
680
+ * Extracted to avoid code duplication between handleApiRequest and handleSyncRequest.
681
+ */
682
+
683
+ /**
684
+ * Check if a value is falsy (false, 0, 0n, '', null, undefined, or NaN)
685
+ */
686
+ declare const isFalsy: (value: unknown) => boolean;
687
+ interface ValidationResult$1 {
688
+ status: 'success' | 'error';
689
+ errorCode?: string;
690
+ errorParams?: {
691
+ key: string;
692
+ value: string | number | boolean;
693
+ }[];
694
+ httpStatus?: number;
695
+ }
696
+ /**
697
+ * Validate a request against authentication requirements.
698
+ *
699
+ * Multiple constraints on a single `additional` entry are AND'd: every
700
+ * specified field (`nullish`, `type`, `value`, `mustBeFalsy`) must pass
701
+ * for that entry to validate. Each entry across the array is also AND'd:
702
+ * the request must pass every entry to succeed.
703
+ *
704
+ * Constraint semantics:
705
+ * - `nullish: true` — `val` must be `null` or `undefined`.
706
+ * - `nullish: false` — `val` must NOT be `null` or `undefined`.
707
+ * - `type: 'string'` — `typeof val === 'string'` (skipped when val is null/undefined).
708
+ * - `value: <x>` — strict equality `val === x`. Omit the field entirely if you
709
+ * don't want this check; setting `value: undefined`
710
+ * explicitly means "val must be undefined".
711
+ * - `mustBeFalsy: true` — `isFalsy(val)`.
712
+ * - `mustBeFalsy: false` — `!isFalsy(val)`.
713
+ */
714
+ declare const validateRequest: ({ auth, user }: {
715
+ auth: AuthProps;
716
+ user: BaseSessionLayout;
717
+ }) => ValidationResult$1;
718
+
719
+ /**
720
+ * Rate Limiter
721
+ *
722
+ * The framework's rate-limit dispatch is pluggable via
723
+ * `registerRateLimitStrategy()`. The built-in `defaultRateLimitStrategy`
724
+ * implements in-memory counters with optional Redis backing for
725
+ * multi-instance deployments. Consumers can plug in alternative strategies
726
+ * (token-bucket, sliding-window, per-user tiers, edge-KV-backed, etc.) by
727
+ * implementing the `RateLimitStrategy` interface.
728
+ */
729
+ interface CheckRateLimitParams {
730
+ /** Unique key for rate limiting (e.g., "user:123:api:getData") */
731
+ key: string;
732
+ /** Maximum requests allowed in window */
733
+ limit: number;
734
+ /** Time window in milliseconds (default: 60_000 = 1 minute) */
735
+ windowMs?: number;
736
+ }
737
+ interface RateLimitResult {
738
+ /** Whether the request is allowed */
739
+ allowed: boolean;
740
+ /** Remaining requests in current window */
741
+ remaining: number;
742
+ /** Seconds until rate limit resets */
743
+ resetIn: number;
744
+ }
745
+ /**
746
+ * Pluggable rate-limit backend. The framework calls these methods through
747
+ * the active strategy slot. Default implementation is memory + Redis.
748
+ * Replace via `registerRateLimitStrategy(...)` at boot.
749
+ */
750
+ interface RateLimitStrategy {
751
+ /** Human-readable identifier (used in logs/diagnostics). */
752
+ name: string;
753
+ /** Increment the counter for `key` and return whether the request is allowed. */
754
+ check(params: CheckRateLimitParams): Promise<RateLimitResult>;
755
+ /** Read current status without incrementing — useful for response headers. */
756
+ getStatus(key: string, limit: number): Promise<RateLimitResult>;
757
+ /** Reset the counter for a single key. */
758
+ clear(key: string): Promise<void>;
759
+ /** Reset all counters under this strategy's namespace. */
760
+ clearAll(): Promise<void>;
761
+ }
762
+ /**
763
+ * Built-in strategy: in-memory counters with optional Redis backing for
764
+ * multi-instance deployments. Mode is controlled by
765
+ * `projectConfig.rateLimiting.store` (`'memory'` or `'redis'`). When `redis`
766
+ * mode fails (e.g. Redis disconnect), the strategy silently degrades to
767
+ * in-memory and logs a one-shot warning.
768
+ */
769
+ declare const defaultRateLimitStrategy: RateLimitStrategy;
770
+ /**
771
+ * Replace the active rate-limit backend. Pass a strategy that implements
772
+ * `RateLimitStrategy`. Common use cases:
773
+ *
774
+ * - Token-bucket / sliding-window algorithms for smoother burst handling.
775
+ * - Per-user tier limits (premium accounts get higher caps).
776
+ * - Cloudflare-Workers-KV or upstash-rest-backed counters for edge.
777
+ * - No-op strategy for test environments.
778
+ *
779
+ * Call this once at boot (typically from `luckystack/server/index.ts`)
780
+ * BEFORE the first request lands. Subsequent calls overwrite the previous
781
+ * registration (last-write-wins).
782
+ */
783
+ declare const registerRateLimitStrategy: (strategy: RateLimitStrategy) => void;
784
+ /** Read the currently-active strategy (defaults to the built-in). */
785
+ declare const getRateLimitStrategy: () => RateLimitStrategy;
786
+ /**
787
+ * Check if a request is allowed under rate limiting rules.
788
+ * Increments the counter for the key if allowed.
789
+ */
790
+ declare const checkRateLimit: (params: CheckRateLimitParams) => Promise<RateLimitResult>;
791
+ /**
792
+ * Get current rate limit status without incrementing counter.
793
+ * Useful for rate limit headers in responses.
794
+ */
795
+ declare const getRateLimitStatus: (key: string, limit: number) => Promise<RateLimitResult>;
796
+ /**
797
+ * Clear rate limit for a specific key.
798
+ * Useful for admin overrides or testing.
799
+ */
800
+ declare const clearRateLimit: (key: string) => Promise<void>;
801
+ /**
802
+ * Clear all rate limits.
803
+ * Useful for testing or server restart.
804
+ */
805
+ declare const clearAllRateLimits: () => Promise<void>;
806
+
807
+ type ValidationResult = {
808
+ status: 'success';
809
+ } | {
810
+ status: 'error';
811
+ message: string;
812
+ };
813
+ declare const validateInputByType: ({ typeText, value, rootKey, filePath, }: {
814
+ typeText?: string;
815
+ value: unknown;
816
+ rootKey: string;
817
+ filePath?: string;
818
+ }) => Promise<ValidationResult>;
819
+
820
+ declare const allowedOrigin: (origin: string) => boolean;
821
+
822
+ declare const registerSyncAbortController: (socketId: string, cb: string, controller: AbortController) => string;
823
+ declare const unregisterSyncAbortController: (key: string) => void;
824
+ declare const abortSyncByCb: (socketId: string, cb: string) => boolean;
825
+ declare const registerApiAbortController: (socketId: string, responseIndex: number | string, controller: AbortController) => string;
826
+ declare const unregisterApiAbortController: (key: string) => void;
827
+ declare const abortApiByResponseIndex: (socketId: string, responseIndex: number | string) => boolean;
828
+ declare const abortAllForSocket: (socketId: string) => void;
829
+
830
+ /**
831
+ * Core hook contract: framework-generic payload shapes + augmentable registry.
832
+ *
833
+ * Feature packages (login/sync/api/presence/...) extend `HookPayloads` via
834
+ * TypeScript module augmentation:
835
+ *
836
+ * ```ts
837
+ * // packages/<feature>/src/hookPayloads.ts
838
+ * declare module '@luckystack/core' {
839
+ * interface HookPayloads {
840
+ * myFeatureHook: { ... };
841
+ * }
842
+ * }
843
+ * export {};
844
+ * ```
845
+ *
846
+ * Module-augmentation files MUST be in a tsconfig `include` path for TS to
847
+ * pick up the merge; a side-effect import from the package's `index.ts`
848
+ * (`import './hookPayloads';`) guarantees this.
849
+ */
850
+
851
+ /**
852
+ * Minimal session shape used inside core-owned hook payloads. Defined here
853
+ * (instead of imported from `@luckystack/login`) so `@luckystack/core` stays
854
+ * independent of login. Any concrete session type (`BaseSessionLayout`,
855
+ * project-level `SessionLayout` extending Prisma User, etc.) is structurally
856
+ * assignable to this shape.
857
+ */
858
+ interface HookSessionShape {
859
+ id: string;
860
+ token: string;
861
+ email?: string | null;
862
+ name?: string | null;
863
+ avatar?: string | null;
864
+ avatarFallback?: string | null;
865
+ admin?: boolean | null;
866
+ language?: string | null;
867
+ roomCodes?: string[];
868
+ }
869
+ interface HookStopSignal {
870
+ stop: true;
871
+ errorCode: string;
872
+ httpStatus?: number;
873
+ }
874
+ /** Handlers return undefined / void to continue, or a stop signal to abort the main flow. */
875
+ type HookResult = void | undefined | HookStopSignal;
876
+ type HookHandler<TPayload> = (payload: TPayload) => Promise<HookResult> | HookResult;
877
+ interface PreApiValidatePayload {
878
+ routeName: string;
879
+ data: Record<string, unknown>;
880
+ user: HookSessionShape | null;
881
+ /**
882
+ * Optional transport tag — populated by the framework's socket/HTTP API
883
+ * handlers so hook subscribers can branch on it (e.g. open span with a
884
+ * different op name). Optional so consumer-side dispatchers don't have to
885
+ * set it explicitly.
886
+ */
887
+ transport?: 'socket' | 'http';
888
+ }
889
+ interface PostApiValidatePayload extends PreApiValidatePayload {
890
+ validation: {
891
+ status: 'success';
892
+ } | {
893
+ status: 'error';
894
+ message: string;
895
+ };
896
+ }
897
+ interface PreApiExecutePayload {
898
+ routeName: string;
899
+ data: Record<string, unknown>;
900
+ user: HookSessionShape | null;
901
+ /** Optional transport tag — see {@link PreApiValidatePayload.transport}. */
902
+ transport?: 'socket' | 'http';
903
+ }
904
+ interface PostApiExecutePayload {
905
+ routeName: string;
906
+ data: Record<string, unknown>;
907
+ user: HookSessionShape | null;
908
+ result: unknown;
909
+ error: Error | null;
910
+ durationMs: number;
911
+ /** Optional transport tag — see {@link PreApiValidatePayload.transport}. */
912
+ transport?: 'socket' | 'http';
913
+ }
914
+ interface ApiResponseEnvelope {
915
+ status: 'success' | 'error';
916
+ httpStatus?: number;
917
+ errorCode?: string;
918
+ message?: string;
919
+ [key: string]: unknown;
920
+ }
921
+ interface PreApiRespondPayload {
922
+ routeName: string;
923
+ user: HookSessionShape | null;
924
+ response: ApiResponseEnvelope;
925
+ /** Optional transport tag — see {@link PreApiValidatePayload.transport}. */
926
+ transport?: 'socket' | 'http';
927
+ }
928
+ interface PostApiRespondPayload {
929
+ routeName: string;
930
+ user: HookSessionShape | null;
931
+ response: ApiResponseEnvelope;
932
+ /** Optional transport tag — see {@link PreApiValidatePayload.transport}. */
933
+ transport?: 'socket' | 'http';
934
+ }
935
+ interface PreSyncAuthorizePayload {
936
+ routeName: string;
937
+ /** Raw (un-validated) data payload. May be reshape-able by the consumer. */
938
+ data: Record<string, unknown>;
939
+ user: HookSessionShape | null;
940
+ /** Room code / receiver token — the sync's intended audience. */
941
+ receiver: string;
942
+ /** Optional transport tag — see {@link PreApiValidatePayload.transport}. */
943
+ transport?: 'socket' | 'http';
944
+ }
945
+ interface PostSyncAuthorizePayload {
946
+ routeName: string;
947
+ data: Record<string, unknown>;
948
+ user: HookSessionShape | null;
949
+ receiver: string;
950
+ /** Optional transport tag — see {@link PreApiValidatePayload.transport}. */
951
+ transport?: 'socket' | 'http';
952
+ }
953
+ interface PreSyncFanoutPayload {
954
+ routeName: string;
955
+ data: Record<string, unknown>;
956
+ user: HookSessionShape | null;
957
+ receiver: string;
958
+ serverOutput: unknown;
959
+ /** Optional transport tag — see {@link PreApiValidatePayload.transport}. */
960
+ transport?: 'socket' | 'http';
961
+ }
962
+ interface PostSyncFanoutPayload extends PreSyncFanoutPayload {
963
+ recipientCount: number;
964
+ }
965
+ interface PreSyncStreamPayload {
966
+ routeName: string;
967
+ /** Stream chunk being sent (already serialised by the framework). */
968
+ chunk: unknown;
969
+ /** Final recipient — socket id when known, room code otherwise. */
970
+ recipient: string;
971
+ }
972
+ interface PostSyncStreamPayload extends PreSyncStreamPayload {
973
+ /** Total chunks streamed so far in this sync invocation. */
974
+ chunkIndex: number;
975
+ }
976
+ interface ApiErrorPayload {
977
+ route: string;
978
+ method?: string;
979
+ requestId?: string;
980
+ user?: HookSessionShape | null;
981
+ error: Error;
982
+ }
983
+ interface SyncErrorPayload {
984
+ route: string;
985
+ method?: string;
986
+ requestId?: string;
987
+ user?: HookSessionShape | null;
988
+ error: Error;
989
+ }
990
+ interface RateLimitExceededPayload {
991
+ /** Where the limit was hit — IP, user, or route. */
992
+ scope: 'ip' | 'user' | 'route' | 'auth';
993
+ /** The key that exceeded its limit (sanitized — no tokens). */
994
+ key: string;
995
+ /** Configured limit count for this key. */
996
+ limit: number;
997
+ /** Window in milliseconds. */
998
+ windowMs: number;
999
+ /** Current bucket count after the rejected request. */
1000
+ count: number;
1001
+ /** Optional route or transport context. */
1002
+ route?: string;
1003
+ ip?: string;
1004
+ userId?: string;
1005
+ }
1006
+ interface CorsRejectedPayload {
1007
+ /** Origin header sent by the client. */
1008
+ origin: string;
1009
+ /** Origin after framework normalization (scheme + host[:port]). */
1010
+ normalizedOrigin: string;
1011
+ /** Effective allowed-origins set at the time of rejection. */
1012
+ allowedOrigins: string[];
1013
+ /** Whether `allowLocalhost` was on. */
1014
+ allowLocalhost: boolean;
1015
+ /** Optional route the rejected request was for. */
1016
+ route?: string;
1017
+ }
1018
+ interface PreSessionRefreshPayload {
1019
+ token: string;
1020
+ userId: string | null;
1021
+ oldTtl: number | null;
1022
+ newTtl: number;
1023
+ }
1024
+ interface PostSessionRefreshPayload {
1025
+ token: string;
1026
+ userId: string | null;
1027
+ oldTtl: number | null;
1028
+ newTtl: number;
1029
+ /** True if Redis EXPIRE succeeded; false on failure or non-existent key. */
1030
+ applied: boolean;
1031
+ }
1032
+ interface OnUploadStartPayload {
1033
+ userId: string;
1034
+ contentType: string;
1035
+ sizeBytes: number;
1036
+ uploadKind: 'avatar' | string;
1037
+ }
1038
+ interface OnUploadCompletePayload {
1039
+ userId: string;
1040
+ fileName: string;
1041
+ sizeBytes: number;
1042
+ uploadKind: 'avatar' | string;
1043
+ }
1044
+ interface CsrfMismatchPayload {
1045
+ /** Path of the rejected request. */
1046
+ route: string;
1047
+ /** HTTP method of the rejected request. */
1048
+ method?: string;
1049
+ /** Request id (X-Request-Id) for log correlation. */
1050
+ requestId?: string;
1051
+ /** User id from the session that the request claimed, if any. */
1052
+ userId?: string;
1053
+ /** Whether a token was provided in `x-csrf-token` (presence only — never the value). */
1054
+ providedToken: boolean;
1055
+ }
1056
+ interface PreHttpRequestPayload {
1057
+ /** HTTP method (always uppercase: GET/POST/...). */
1058
+ method: string;
1059
+ /** Request URL (path + query). */
1060
+ url: string;
1061
+ /** Caller-supplied or framework-generated `X-Request-Id`. */
1062
+ requestId: string;
1063
+ /** Origin header value (or empty string when absent). */
1064
+ origin: string;
1065
+ /**
1066
+ * Subset of headers — `authorization`, `cookie`, etc. are NOT included
1067
+ * to keep payloads safe for logging. Use the full request object via
1068
+ * a custom route if you need raw access.
1069
+ */
1070
+ headers: Record<string, string>;
1071
+ }
1072
+ interface HookPayloads {
1073
+ preHttpRequest: PreHttpRequestPayload;
1074
+ preApiValidate: PreApiValidatePayload;
1075
+ postApiValidate: PostApiValidatePayload;
1076
+ preApiExecute: PreApiExecutePayload;
1077
+ postApiExecute: PostApiExecutePayload;
1078
+ preApiRespond: PreApiRespondPayload;
1079
+ transformApiResponse: PreApiRespondPayload;
1080
+ postApiRespond: PostApiRespondPayload;
1081
+ preSyncAuthorize: PreSyncAuthorizePayload;
1082
+ postSyncAuthorize: PostSyncAuthorizePayload;
1083
+ preSyncFanout: PreSyncFanoutPayload;
1084
+ postSyncFanout: PostSyncFanoutPayload;
1085
+ preSyncStream: PreSyncStreamPayload;
1086
+ postSyncStream: PostSyncStreamPayload;
1087
+ apiError: ApiErrorPayload;
1088
+ syncError: SyncErrorPayload;
1089
+ rateLimitExceeded: RateLimitExceededPayload;
1090
+ corsRejected: CorsRejectedPayload;
1091
+ csrfMismatch: CsrfMismatchPayload;
1092
+ preSessionRefresh: PreSessionRefreshPayload;
1093
+ postSessionRefresh: PostSessionRefreshPayload;
1094
+ onUploadStart: OnUploadStartPayload;
1095
+ onUploadComplete: OnUploadCompletePayload;
1096
+ }
1097
+ interface PreErrorNormalizePayload {
1098
+ response: ErrorResponseInput;
1099
+ preferredLocale?: string | null;
1100
+ userLanguage?: string | null;
1101
+ fallbackHttpStatus?: number;
1102
+ }
1103
+ interface PostErrorNormalizePayload {
1104
+ normalized: NormalizedErrorResponse;
1105
+ preferredLocale?: string | null;
1106
+ userLanguage?: string | null;
1107
+ }
1108
+ interface SyncHookPayloads {
1109
+ preErrorNormalize: PreErrorNormalizePayload;
1110
+ postErrorNormalize: PostErrorNormalizePayload;
1111
+ }
1112
+ type SyncHookName = keyof SyncHookPayloads;
1113
+ type SyncHookHandler<TPayload> = (payload: TPayload) => void;
1114
+ type HookName = keyof HookPayloads;
1115
+
1116
+ type DispatchResult = {
1117
+ stopped: false;
1118
+ } | {
1119
+ stopped: true;
1120
+ signal: HookStopSignal;
1121
+ };
1122
+ declare const registerHook: <TName extends HookName>(name: TName, handler: HookHandler<HookPayloads[TName]>) => void;
1123
+ declare const dispatchHook: <TName extends HookName>(name: TName, payload: HookPayloads[TName]) => Promise<DispatchResult>;
1124
+ declare const clearAllHooks: () => void;
1125
+ declare const registerSyncHook: <TName extends SyncHookName>(name: TName, handler: SyncHookHandler<SyncHookPayloads[TName]>) => void;
1126
+ declare const dispatchSyncHook: <TName extends SyncHookName>(name: TName, payload: SyncHookPayloads[TName]) => void;
1127
+
1128
+ export { type ApiErrorPayload, type ApiResponseEnvelope, AuthProps, type AvatarConfig, type AvatarConfigInput, BOOT_KEY_PREFIX, BaseSessionLayout, type CheckRateLimitParams, type CorsRejectedPayload, type CreateLocalizedNormalizerInput, type CsrfConfig, type CsrfCookieOptions, type CsrfMismatchPayload, DEFAULT_AVATAR_CONFIG, DEFAULT_CLIENT_KEY, DEFAULT_CSRF_CONFIG, DEFAULT_ENV_FILES, type DeployConfigShape, type DeployDevelopmentShape, type DeployEnvironmentShape, type DeployResourceShape, type DeployRoutingShape, type DispatchResult, type EmailMessage, type EmailResult, type EmailSender, type EmailSenderRegistry, type ErrorFormatter, type ErrorFormatterContext, ErrorParam, ErrorResponseInput, type ErrorTracker, type ErrorTrackerContext, type ErrorTrackerEvent, type ErrorTrackerUser, GENERATED_API_DOCS_PATH, GENERATED_API_SCHEMAS_PATH, GENERATED_SOCKET_TYPES_PATH, type HookHandler, type HookName, type HookPayloads, type HookResult, type HookSessionShape, type HookStopSignal, type HttpMethod, type LanguageCode, type LocaleReloader, type LocalizedNormalizer, type NormalizeErrorResponseInput, NormalizedErrorResponse, type OnUploadCompletePayload, type OnUploadStartPayload, PUBLIC_DIR, type PostApiExecutePayload, type PostApiRespondPayload, type PostApiValidatePayload, type PostErrorNormalizePayload, type PostSessionRefreshPayload, type PostSyncFanoutPayload, type PreApiExecutePayload, type PreApiRespondPayload, type PreApiValidatePayload, type PreErrorNormalizePayload, type PreSessionRefreshPayload, type PreSyncAuthorizePayload, type PreSyncFanoutPayload, type PresetDefinition, type ProcessUploadInput, type ProcessUploadResult, ROOT_DIR, type RateLimitExceededPayload, type RateLimitResult, type RateLimitStrategy, type RedisConnectionOptions, type RedisKeyFormatter, type RuntimeApiMapsResult, type RuntimeEnv, type RuntimeMapsProvider, type RuntimeSyncMapsResult, SERVER_DIR, SERVER_FUNCTIONS_DIR, SHARED_DIR, SRC_DIR, type ServiceDefinition, type ServicesConfigShape, type SocketMiddleware, type SpanResult, type SyncErrorPayload, type SyncHookHandler, type SyncHookName, type SyncHookPayloads, TSCONFIG_ALIAS_FILES, type TranslateFunction, type TranslateInput, UPLOADS_DIR, type ValidationResult$1 as ValidationResult, abortAllForSocket, abortApiByResponseIndex, abortSyncByCb, acquireLease, allowedOrigin, type apiMessage, applyErrorFormatter, applySocketMiddlewares, applyStrayKeyPrefix, attachSocketRedisAdapter, bootstrapEnv, captureException, captureExceptionAcrossTrackers, captureMessage, captureMessageAcrossTrackers, checkRateLimit, clearAllHooks, clearAllRateLimits, clearRateLimit, clearSocketMiddlewares, collectSynchronizedEnvKeys, computeSynchronizedEnvHashes, createLocalizedNormalizer, defaultRateLimitStrategy, defaultRedisKeyFormatter, dispatchHook, dispatchSyncHook, env, extractLanguageFromHeader, extractTokenFromRequest, extractTokenFromSocket, formatKey, getActiveErrorTrackers, getAvatarConfig, getBindAddress, getCookieValue, getCsrfConfig, getDeployConfig, getEffectiveHttpMethod, getEmailSender, getEmailSenderByName, getEnvFiles, getErrorFormatter, getGeneratedApiDocsPath, getGeneratedApiSchemasPath, getGeneratedSocketTypesPath, getIoInstance, getLocaleReloader, getLocalizedNormalizer, getParams, getPrismaClient, getPrismaClientFor, getPrismaClientKeys, getPublicDir, getRateLimitStatus, getRateLimitStrategy, getRedactedLogKeys, getRedisClient, getRedisClientFor, getRedisClientKeys, getRedisConnectionOptions, getRedisKeyFormatter, getRuntimeApiMaps, getRuntimeSyncMaps, getServerDir, getServerFunctionDirs, getServerFunctionsDir, getServicesConfig, getSharedDir, getSocketMiddlewares, getSrcDir, getUploadsDir, hasCookie, hashSynchronizedValue, inferHttpMethod, initConsolelog, initSharedSentry, isDeployConfigRegistered, isEmailSenderRegistered, isFalsy, isLocalizedNormalizerRegistered, isMethodAllowed, isPrismaClientRegistered, isProduction, isRedactedLogKey, isRedisClientRegistered, isRuntimeMapsProviderRegistered, isServicesConfigRegistered, listEmailSenderNames, loadEnvFiles, normalizeErrorResponse, prisma, processUpload, readBootUuid, recordMetricAcrossTrackers, redis, registerApiAbortController, registerAvatarConfig, registerBindAddress, registerCsrfConfig, registerDeployConfig, registerEmailSender, registerEmailSenders, registerErrorFormatter, registerErrorTracker, registerErrorTrackers, registerHook, registerLocaleReloader, registerLocalizedNormalizer, registerPrismaClient, registerRateLimitStrategy, registerRedactedLogKeys, registerRedisClient, registerRedisKeyFormatter, registerRuntimeMapsProvider, registerServicesConfig, registerSocketMiddleware, registerSyncAbortController, registerSyncHook, releaseLease, renewLease, resetClientsForTests, resetCsrfConfigForTests, resetRedactedLogKeysForTests, resetRedisKeyFormatterForTests, resolveEnvKey, resolveErrorMessage, resolveFromRoot, serveAvatar, setErrorTrackerUser, setIoInstance, setSentryUser, startSpan, startSpanAcrossTrackers, type syncMessage, unregisterApiAbortController, unregisterSyncAbortController, validateInputByType, validateRequest, writeBootUuid };