@luckystack/core 0.10.2 → 0.12.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.
Files changed (134) hide show
  1. package/AGENTS.md +11 -0
  2. package/CHANGELOG.md +18 -0
  3. package/README.md +2 -88
  4. package/dist/apiInterceptors.d.ts +36 -0
  5. package/dist/apiMethodMapRegistry.d.ts +5 -0
  6. package/dist/apiRequest.d.ts +71 -0
  7. package/dist/apiTypeStubs.d.ts +3 -5
  8. package/dist/avatarConfig.d.ts +22 -0
  9. package/dist/bindAddress.d.ts +15 -0
  10. package/dist/bootUuid.d.ts +24 -0
  11. package/dist/cancelRegistry.d.ts +7 -0
  12. package/dist/checkOrigin.d.ts +6 -0
  13. package/dist/{chunk-IJYNVM2W.js → chunk-P3SSLA3E.js} +2 -2
  14. package/dist/chunk-P3SSLA3E.js.map +1 -0
  15. package/dist/{chunk-5FXBTGYL.js → chunk-XDX5MV54.js} +2 -2
  16. package/dist/chunk-XDX5MV54.js.map +1 -0
  17. package/dist/client.d.ts +46 -335
  18. package/dist/client.js +5 -5
  19. package/dist/client.js.map +1 -1
  20. package/dist/clientHookBus.d.ts +62 -0
  21. package/dist/clients.d.ts +16 -0
  22. package/dist/config.d.ts +8 -2
  23. package/dist/configUtils.d.ts +16 -0
  24. package/dist/consoleLog.d.ts +1 -0
  25. package/dist/cookies.d.ts +28 -0
  26. package/dist/createRegistry.d.ts +39 -0
  27. package/dist/csrf.d.ts +30 -0
  28. package/dist/csrfConfig.d.ts +32 -0
  29. package/dist/db.d.ts +12 -0
  30. package/dist/dbHealthCheck.d.ts +6 -0
  31. package/dist/{config-CwW3KY9r.d.ts → deployConfigRegistry.d.ts} +8 -43
  32. package/dist/emailRegistry.d.ts +139 -0
  33. package/dist/env.d.ts +38 -0
  34. package/dist/errorFormatterRegistry.d.ts +54 -0
  35. package/dist/errorTrackerIdentity.d.ts +14 -0
  36. package/dist/errorTrackerRegistry.d.ts +85 -0
  37. package/dist/escapeHtml.d.ts +6 -0
  38. package/dist/eslint/index.d.ts +5 -8
  39. package/dist/eslint/index.js +125 -8
  40. package/dist/eslint/index.js.map +1 -1
  41. package/dist/eslint/internal/hasPackage.d.ts +1 -0
  42. package/dist/eslint/internal/ruleTypes.d.ts +4 -0
  43. package/dist/eslint/rules/no-arbitrary-tailwind-color.d.ts +3 -0
  44. package/dist/eslint/rules/no-direct-prisma-import-in-components.d.ts +3 -0
  45. package/dist/eslint/rules/no-local-socket-enumeration.d.ts +3 -0
  46. package/dist/eslint/rules/no-raw-fetch-in-src.d.ts +3 -0
  47. package/dist/eslint/rules/no-raw-try-catch.d.ts +3 -0
  48. package/dist/eslint/rules/no-unsafe-api-wrappers.d.ts +3 -0
  49. package/dist/eslint/rules/no-unsafe-sync-wrappers.d.ts +3 -0
  50. package/dist/eslint/rules/no-untranslated-jsx-text.d.ts +4 -0
  51. package/dist/eslint/rules/prefer-luckystack-confirm.d.ts +3 -0
  52. package/dist/eslint/rules/prefer-luckystack-dropdown.d.ts +3 -0
  53. package/dist/eslint/rules/prefer-luckystack-notify.d.ts +3 -0
  54. package/dist/extractToken.d.ts +12 -0
  55. package/dist/extractTokenFromRequest.d.ts +12 -0
  56. package/dist/getParams.d.ts +9 -0
  57. package/dist/hooks/registry.d.ts +12 -0
  58. package/dist/hooks/types.d.ts +446 -0
  59. package/dist/httpApiUtils.d.ts +54 -0
  60. package/dist/index.d.ts +110 -1892
  61. package/dist/index.js +2 -2
  62. package/dist/index.js.map +1 -1
  63. package/dist/lease.d.ts +18 -0
  64. package/dist/localSocketEnumerationGuard.d.ts +22 -0
  65. package/dist/localeReloader.d.ts +3 -0
  66. package/dist/localesRegistry.d.ts +8 -0
  67. package/dist/localizedNormalizer.d.ts +50 -0
  68. package/dist/loggerRegistry.d.ts +12 -0
  69. package/dist/middlewareRegistry.d.ts +45 -0
  70. package/dist/notifier.d.ts +17 -0
  71. package/dist/offlineQueue.d.ts +48 -0
  72. package/dist/oneTimeToken.d.ts +43 -0
  73. package/dist/pageRouteValidation.d.ts +37 -0
  74. package/dist/paths.d.ts +35 -0
  75. package/dist/peerDeps.d.ts +23 -0
  76. package/dist/portOverrideRegistry.d.ts +3 -0
  77. package/dist/processUpload.d.ts +44 -0
  78. package/dist/{sessionTypes-DanjuPaE.d.ts → projectConfig.d.ts} +32 -129
  79. package/dist/rateLimiter.d.ts +90 -0
  80. package/dist/react/AvatarProvider.d.ts +11 -0
  81. package/dist/react/Middleware.d.ts +10 -0
  82. package/dist/react/Router.d.ts +1 -0
  83. package/dist/react/TranslationProvider.d.ts +22 -0
  84. package/dist/react/notify.d.ts +12 -0
  85. package/dist/react/sessionContext.d.ts +31 -0
  86. package/dist/react/useTheme.d.ts +5 -0
  87. package/dist/redactedLogKeys.d.ts +22 -0
  88. package/dist/redis.d.ts +15 -0
  89. package/dist/redisKeyFormatter.d.ts +7 -0
  90. package/dist/resolveClientIp.d.ts +51 -0
  91. package/dist/responseNormalizer.d.ts +33 -0
  92. package/dist/roomNameFormatterRegistry.d.ts +36 -0
  93. package/dist/roomSockets.d.ts +23 -0
  94. package/dist/routedDataQuery.d.ts +7 -0
  95. package/dist/routedHttpInvocation.d.ts +20 -0
  96. package/dist/runtimeMapsRegistry.d.ts +18 -0
  97. package/dist/runtimeTypeValidation.d.ts +13 -0
  98. package/dist/secretsResolved.d.ts +3 -0
  99. package/dist/{sentrySetup-HNIURVUZ.js → sentrySetup-OLZ4BO7Q.js} +2 -2
  100. package/dist/sentrySetup.d.ts +21 -0
  101. package/dist/serveAvatars.d.ts +5 -0
  102. package/dist/serviceRoute.d.ts +27 -0
  103. package/dist/servicesConfigRegistry.d.ts +22 -0
  104. package/dist/sessionProviderRegistry.d.ts +46 -0
  105. package/dist/sessionTypes.d.ts +79 -0
  106. package/dist/sleep.d.ts +1 -0
  107. package/dist/socketEvents.d.ts +37 -0
  108. package/dist/socketMiddlewareRegistry.d.ts +20 -0
  109. package/dist/socketRedisAdapter.d.ts +21 -0
  110. package/dist/socketState.d.ts +5 -0
  111. package/dist/socketStatusTypes.d.ts +6 -0
  112. package/dist/socketTypes.d.ts +34 -0
  113. package/dist/synchronizedEnvHashes.d.ts +66 -0
  114. package/dist/testFileConvention.d.ts +11 -0
  115. package/dist/testResetTargets.d.ts +5 -0
  116. package/dist/tryCatch.d.ts +1 -0
  117. package/dist/tryCatchClient.d.ts +1 -0
  118. package/dist/tryCatchSync.d.ts +1 -0
  119. package/dist/validateRequest.d.ts +50 -0
  120. package/docs/app-bootstrap.md +4 -449
  121. package/docs/config-registry.md +3 -346
  122. package/docs/csrf-config.md +3 -86
  123. package/docs/error-tracker-registry.md +3 -192
  124. package/docs/hooks.md +2 -295
  125. package/docs/rate-limit-strategy.md +3 -168
  126. package/docs/redis-adapter.md +5 -110
  127. package/docs/session-types.md +3 -178
  128. package/docs/socket-bootstrap.md +5 -385
  129. package/package.json +4 -4
  130. package/CLAUDE.md +0 -256
  131. package/dist/chunk-5FXBTGYL.js.map +0 -1
  132. package/dist/chunk-IJYNVM2W.js.map +0 -1
  133. package/dist/pageRouteValidation-B4Cmtfjv.d.ts +0 -280
  134. /package/dist/{sentrySetup-HNIURVUZ.js.map → sentrySetup-OLZ4BO7Q.js.map} +0 -0
package/dist/index.d.ts CHANGED
@@ -1,1892 +1,110 @@
1
- import { _ as ErrorParam, $ as ErrorResponseInput, a0 as NormalizedErrorResponse } from './pageRouteValidation-B4Cmtfjv.js';
2
- export { A as ApiMethodMap, D as DEFAULT_PAGE_ROUTE_RULES, H as HttpMethodLiteral, a1 as INVALID_ERROR_RESPONSE_CODE, L as Logger, a as LoggerContext, N as Notifier, b as NotifyInput, c as NotifyParam, P as PagePathValidationResult, d as PageRouteRules, a2 as QueueDropReason, R as ROUTED_DATA_QUERY_KEY, S as SOCKETSTATUS, a3 as ServiceRouteParseError, a4 as ServiceRouteParseResult, a5 as ServiceRouteParseSuccess, a6 as TransportRouteParseError, a7 as TransportRouteParseResult, a8 as TransportRouteParseSuccess, e as buildApiResponseEventName, f as buildApiStreamEventName, g as buildGetJoinedRoomsResponseEventName, h as buildJoinRoomResponseEventName, i as buildLeaveRoomResponseEventName, j as buildRoutedGetUrl, k as buildSyncProgressEventName, l as buildSyncResponseEventName, m as clearCsrfToken, a9 as createDevLogger, aa as defaultHttpStatusForResponse, n as enqueueApiRequest, o as enqueueSyncRequest, p as flushApiQueue, q as flushSyncQueue, r as getApiQueueSize, s as getCsrfToken, t as getLogger, u as getNotifier, v as getRegisteredApiMethod, w as getSyncQueueSize, x as httpFetch, y as incrementResponseIndex, z as isApiMethodMapRegistered, ab as isErrorParamArray, ac as isLoggerRegistered, B as isOnline, C as normalizeErrorResponseCore, E as notify, F as parseServiceRouteName, ad as parseTransportRouteName, G as registerApiMethodMap, I as registerLogger, J as registerNotifier, K as removeApiQueueItem, M as removeApiQueueItemsByKey, O as removeSyncQueueItem, ae as resetLoggerForTests, Q as setSocket, T as sleep, U as socket, V as socketEventNames, W as statusContent, X as tryCatchSync, Y as validatePagePath, Z as waitForSocket } from './pageRouteValidation-B4Cmtfjv.js';
3
- import { H as HealthHashConfig, D as DeepPartial, B as BaseSessionLayout, a as AuthProps } from './sessionTypes-DanjuPaE.js';
4
- export { c as ApiConfig, A as AppConfig, d as AuthConfig, e as AuthRateLimitConfig, C as CorsConfig, f as DEFAULT_PROJECT_CONFIG, h as DevConfig, j as HttpConfig, k as HttpStreamConfig, J as Jsonify, L as LoggingConfig, O as OfflineQueueConfig, l as PasswordPolicyConfig, m as PathsConfig, P as ProjectConfig, n as ProjectConfigInput, o as RateLimitIdentity, p as RateLimitIdentityParams, R as RateLimitingConfig, q as SecurityHeadersConfig, S as SessionConfig, b as SessionLocation, s as SocketConfig, t as SyncConfig, u as SyncFlushPressureConfig, v as SyncStreamThrottleConfig, V as ValidationConfig, w as deepMerge, g as getProjectConfig, x as getProjectName, y as isPlainObject, i as isProjectConfigRegistered, r as registerProjectConfig } from './sessionTypes-DanjuPaE.js';
5
- export { ApiTypeMap, StreamPayload, SyncTypeMap } from './apiTypeStubs.js';
6
- import { Server, Socket } from 'socket.io';
7
- export { D as DeployConfigShape, a as DeployDevelopmentShape, b as DeployEnvironmentShape, c as DeployResourceShape, d as DeployRoutingShape, P as PresetDefinition, S as SecretsResolvedListener, e as ServiceDefinition, f as ServicesConfigShape, g as getDeployConfig, h as getPortOverride, i as getServicesConfig, j as isDeployConfigRegistered, k as isServicesConfigRegistered, n as notifySecretsResolved, r as registerDeployConfig, l as registerPortOverride, m as registerSecretsResolvedListener, o as registerServicesConfig, p as resetPortOverrideForTests } from './config-CwW3KY9r.js';
8
- import { z } from 'zod';
9
- import * as _prisma_client_runtime_library from '@prisma/client/runtime/library';
10
- import * as _prisma_client from '@prisma/client';
11
- import { PrismaClient } from '@prisma/client';
12
- import Redis$1, { Redis } from 'ioredis';
13
- import { RedisAdapterOptions } from '@socket.io/redis-adapter';
14
- import { ServerResponse, IncomingMessage, IncomingHttpHeaders } from 'node:http';
15
- import 'socket.io-client';
16
-
17
- declare function tryCatch<T, P = void>(func: (values: P) => Promise<T> | T, params?: P, context?: Record<string, unknown>): Promise<[Error | null, T | null]>;
18
-
19
- type RequireTransform<TInput, TStored> = [TInput] extends [TStored] ? {
20
- transform?: (input: TInput, current: TStored) => TStored;
21
- } : {
22
- transform: (input: TInput, current: TStored) => TStored;
23
- };
24
- interface RegistryOptionsBase<TStored> {
25
- /**
26
- * Called once per `register` with the freshly-stored value — used for the
27
- * rate-limiter's `[RateLimiter] active strategy → …` debug line. Runs after
28
- * the slot is updated so a logger registry can log through itself.
29
- */
30
- onRegister?: (stored: TStored) => void;
31
- /**
32
- * Resolve the value `get` returns when nothing has been registered yet.
33
- * Defaults to returning `defaultValue`. `servicesConfig` throws here.
34
- */
35
- resolveDefault?: () => TStored;
36
- }
37
- type RegistryOptions<TInput, TStored> = RegistryOptionsBase<TStored> & RequireTransform<TInput, TStored>;
38
- interface Registry<TInput, TStored> {
39
- /** Store a value (after the optional `transform`) and mark the slot registered. */
40
- register: (input: TInput) => TStored;
41
- /** Read the active value at call time (registered value, else the default). */
42
- get: () => TStored;
43
- /** True once `register` has been called at least once. */
44
- isRegistered: () => boolean;
45
- /** Restore the unregistered state (test-only). */
46
- reset: () => void;
47
- }
48
- /**
49
- * Create a single-slot registry. `defaultValue` is returned by `get` until
50
- * `register` is called (unless `resolveDefault` overrides that). When
51
- * `TInput` differs from `TStored`, the `transform` option is mandatory (the
52
- * `RequireTransform` constraint enforces it); when they match it is optional
53
- * and defaults to identity. The second positional argument is likewise
54
- * optional only in the identity case.
55
- */
56
- declare function createRegistry<TStored, TInput = TStored>(defaultValue: TStored, ...optionsArg: [TInput] extends [TStored] ? [options?: RegistryOptions<TInput, TStored>] : [options: RegistryOptions<TInput, TStored>]): Registry<TInput, TStored>;
57
-
58
- /**
59
- * Escape `&`, `<`, `>`, `"`, `'` for safe interpolation into HTML text nodes
60
- * and quoted attribute values. Does NOT protect against CSS/JS/URL-context
61
- * injection — use a dedicated sanitizer for those contexts.
62
- */
63
- declare const escapeHtml: (str: string) => string;
64
-
65
- /** Minimal shape of a Node `require` with the `.resolve` member these guards use. */
66
- interface PeerRequire {
67
- (id: string): unknown;
68
- resolve: (id: string) => string;
69
- }
70
- /**
71
- * Assert that an optional peer package is installed, throwing a consistent,
72
- * actionable error when it is not. Does NOT load the module — use `loadPeer`
73
- * for that. Pass `requireFn` (a `createRequire(import.meta.url)` from the
74
- * calling adapter) so resolution happens from the consumer's perspective.
75
- *
76
- * `hint` is appended to the standard "package X is not installed" sentence —
77
- * e.g. `'Run `npm install resend`, or pick a different EmailSender adapter.'`.
78
- */
79
- declare const ensurePeerDepInstalled: (packageName: string, hint: string, requireFn?: PeerRequire) => void;
80
- /**
81
- * Synchronously load an optional peer package, asserting it is installed
82
- * first (so a missing dependency fails with the actionable `hint` rather than
83
- * a raw `MODULE_NOT_FOUND`). Returns the module typed as `T`. Pass `requireFn`
84
- * (built with `createRequire(import.meta.url)` at the call site) so resolution
85
- * + load happen relative to the calling module.
86
- */
87
- declare const loadPeer: <T>(packageName: string, hint: string, requireFn?: PeerRequire) => T;
88
-
89
- /** Matches `<name>.test|tests|spec.<ts|tsx|js|jsx|mts|cts|mjs|cjs>`. */
90
- declare const TEST_FILE_PATTERN: RegExp;
91
- /** Directory names that conventionally hold tests and never runtime modules. */
92
- declare const TEST_DIRECTORY_NAMES: Set<string>;
93
- /**
94
- * True when the given file name (or path — only the last segment is examined)
95
- * is a test file by the conventions above.
96
- */
97
- declare const isTestFile: (fileNameOrPath: string) => boolean;
98
- /** True when a directory name conventionally holds tests rather than runtime modules. */
99
- declare const isTestDirectory: (directoryName: string) => boolean;
100
-
101
- interface RoomNameFormatterContext {
102
- /**
103
- * Which room FAMILY the name is being formatted for — a formatter may branch on
104
- * this to isolate genuinely-separate families, but MUST NOT use it to give the
105
- * same raw room code different physical names across a single family's
106
- * operations. A content room is joined, left, membership-checked, broadcast to,
107
- * and streamed to under the SINGLE canonical `'broadcast'` purpose (M5); the
108
- * framework never varies a content room's physical name by operation, because a
109
- * socket physically IN `f(R,'join')` could never receive a fanout targeting
110
- * `f(R,'broadcast')`. `'presence'` is the one genuinely-separate family (its own
111
- * rooms). `'join'`/`'leave'` remain in the union for backward-compat but the
112
- * framework no longer emits them for room-NAME formatting — treat them, if ever
113
- * seen, identically to `'broadcast'`.
114
- */
115
- purpose: 'join' | 'leave' | 'broadcast' | 'presence';
116
- /**
117
- * Session user id of whoever is performing THIS operation, when known, else
118
- * null — CONTEXT ONLY. For a shared content room under `'broadcast'` the
119
- * framework passes the joiner on join/rejoin, the SENDER on fan-out and the
120
- * originator on `broadcastStream`, so the value differs per call for the SAME
121
- * room. A formatter must therefore never fold `userId` into a content room's
122
- * physical name: join and fan-out would resolve to different rooms and every
123
- * broadcast would reach nobody, silently. Per-user isolation belongs in the
124
- * raw room code the consumer chooses, or in the separate `'presence'` family.
125
- */
126
- userId?: string | null;
127
- }
128
- type RoomNameFormatter = (rawRoomName: string, ctx: RoomNameFormatterContext) => string;
129
- /** Override how every framework room name is namespaced (multi-tenant prefixing). */
130
- declare const registerRoomNameFormatter: (formatter: RoomNameFormatter) => void;
131
- /** Read the active formatter (defaults to identity). */
132
- declare const getRoomNameFormatter: () => RoomNameFormatter;
133
- /** Apply the active formatter — the single helper framework room-sites call. */
134
- declare const formatRoomName: (rawRoomName: string, ctx: RoomNameFormatterContext) => string;
135
- /** The built-in identity formatter (exported for tests / explicit reset). */
136
- declare const defaultRoomNameFormatter: RoomNameFormatter;
137
-
138
- type LanguageCode = string;
139
- interface TranslateInput {
140
- language: LanguageCode;
141
- key: string;
142
- params?: ErrorParam[];
143
- }
144
- type TranslateFunction = (input: TranslateInput) => string;
145
- interface NormalizeErrorResponseInput {
146
- response: ErrorResponseInput;
147
- preferredLocale?: string | null;
148
- userLanguage?: string | null;
149
- fallbackHttpStatus?: number;
150
- }
151
- interface LocalizedNormalizer {
152
- normalizeErrorResponse: (input: NormalizeErrorResponseInput) => NormalizedErrorResponse;
153
- resolveErrorMessage: (input: {
154
- errorCode: string;
155
- errorParams?: ErrorParam[];
156
- preferredLocale?: string | null;
157
- userLanguage?: string | null;
158
- }) => string;
159
- extractLanguageFromHeader: (header?: string | string[]) => LanguageCode | null;
160
- }
161
- interface CreateLocalizedNormalizerInput {
162
- translate: TranslateFunction;
163
- /**
164
- * Language the framework falls back to when the caller provides no
165
- * language hints. Defaults to 'en'.
166
- */
167
- defaultLanguage?: LanguageCode;
168
- /**
169
- * Decide whether a given string is an acceptable language code. Defaults
170
- * to accepting any non-empty short-form code (ISO 639-1 style). Projects
171
- * that only support a closed set should pass a narrower predicate.
172
- */
173
- isSupportedLanguage?: (code: string) => boolean;
174
- }
175
- declare const createLocalizedNormalizer: (input: CreateLocalizedNormalizerInput) => LocalizedNormalizer;
176
- declare const registerLocalizedNormalizer: (normalizer: LocalizedNormalizer) => void;
177
- declare const getLocalizedNormalizer: () => LocalizedNormalizer;
178
- declare const isLocalizedNormalizerRegistered: () => boolean;
179
- declare const normalizeErrorResponse: (input: NormalizeErrorResponseInput) => NormalizedErrorResponse;
180
- declare const resolveErrorMessage: (input: {
181
- errorCode: string;
182
- errorParams?: ErrorParam[];
183
- preferredLocale?: string | null;
184
- userLanguage?: string | null;
185
- }) => string;
186
- declare const extractLanguageFromHeader: (header?: string | string[]) => LanguageCode | null;
187
-
188
- interface ErrorFormatterContext {
189
- /** Resolved route name (`api/billing/getInvoice/v1`, `sync/...`). */
190
- routeName: string;
191
- /** Transport that triggered the error. */
192
- transport: 'socket' | 'http';
193
- /** Session id when available. */
194
- userId?: string | null;
195
- }
196
- /** The normalized error envelope handed to every formatter. */
197
- interface FormatterErrorEnvelope {
198
- status: 'error';
199
- errorCode?: string;
200
- message?: string;
201
- httpStatus?: number;
202
- [key: string]: unknown;
203
- }
204
- type ErrorFormatter = (error: FormatterErrorEnvelope, ctx: ErrorFormatterContext) => Record<string, unknown>;
205
- /**
206
- * Minimal structural contract every formatter-eligible envelope satisfies:
207
- * a discriminant `status` plus an open string-index so formatters can read /
208
- * add arbitrary fields. Generic helpers below constrain to this so transport
209
- * handlers no longer need `as unknown as` double-casts at the formatter
210
- * boundary — see `applyErrorFormatter` / `buildFormattedError`.
211
- */
212
- interface FormatterEnvelope {
213
- status?: string;
214
- [key: string]: unknown;
215
- }
216
- /**
217
- * Register a global error-response formatter. Receives the normalized
218
- * error envelope + context; return a (possibly extended) object that the
219
- * framework will emit. Pass `null` to unregister.
220
- *
221
- * For per-endpoint overrides, export `errorFormatter` from your
222
- * `_api/foo_v1.ts` file directly — the api/sync handler imports + invokes
223
- * it through `applyErrorFormatter`.
224
- */
225
- declare const registerErrorFormatter: (formatter: ErrorFormatter | null) => void;
226
- /** Read the active global formatter (or null). */
227
- declare const getErrorFormatter: () => ErrorFormatter | null;
228
- /**
229
- * Apply per-route → global → identity formatter chain to a normalized
230
- * error envelope. Returns the envelope unchanged when `status !== 'error'`
231
- * so it's safe to wrap every emit call. Errors thrown inside a formatter
232
- * are caught and logged; the un-formatted envelope is returned in that
233
- * case to keep the error path crash-resistant.
234
- */
235
- declare const applyErrorFormatter: <T extends FormatterEnvelope>(input: {
236
- response: T;
237
- routeName: string;
238
- transport: "socket" | "http";
239
- userId?: string | null;
240
- perRouteFormatter?: ErrorFormatter | undefined | null;
241
- }) => T;
242
-
243
- interface apiMessage {
244
- name: string;
245
- data: object;
246
- responseIndex: number;
247
- }
248
- interface syncMessage {
249
- name: string;
250
- data: object;
251
- cb: string;
252
- receiver: string;
253
- responseIndex?: number;
254
- ignoreSelf?: boolean;
255
- }
256
- declare const setIoInstance: (io: Server | null) => void;
257
- interface GetIoInstanceOptions {
258
- /**
259
- * Return the raw Socket.io server even outside production. Reach for this
260
- * ONLY when the per-instance view is what you want (backpressure sampling, a
261
- * sweep cleaning up this process's own connections) — and say why in a
262
- * comment, because the lint rule `luckystack/no-local-socket-enumeration`
263
- * will still flag the access.
264
- */
265
- raw?: boolean;
266
- }
267
- /**
268
- * Read the running Socket.io server. Outside production the returned value is
269
- * a guarded view that THROWS on the per-instance surfaces
270
- * (`sockets.adapter.rooms`, `sockets.adapter.sids`, enumerating
271
- * `sockets.sockets`) — see `localSocketEnumerationGuard.ts` for why. Pass
272
- * `{ raw: true }` for the unwrapped server. In production the raw server is
273
- * always returned; the guard costs nothing there.
274
- */
275
- declare const getIoInstance: (options?: GetIoInstanceOptions) => Server | null;
276
-
277
- /** Thrown (outside production) when consumer code touches a per-instance socket map. */
278
- declare class LocalSocketEnumerationError extends Error {
279
- constructor(surface: string);
280
- }
281
-
282
- /** A recipient as returned by Socket.io's adapter-aware `fetchSockets()`. */
283
- type RoomSocket = Awaited<ReturnType<Server['fetchSockets']>>[number];
284
- interface GetRoomSocketsOptions {
285
- /**
286
- * User id forwarded to the room-name formatter as CONTEXT. Leave it out (or
287
- * pass `null`) for a shared room — the formatter contract forbids folding it
288
- * into a content room's physical name, so it only matters to a formatter that
289
- * logs or branches on it.
290
- */
291
- userId?: string | null;
292
- }
293
- /**
294
- * Resolve every socket currently in `room`, on EVERY instance sharing the
295
- * Redis adapter. `room` is the LOGICAL code (what the client joined); it is
296
- * routed through the registered room-name formatter under the canonical
297
- * `'broadcast'` purpose exactly like the sync fan-out. The `'all'` sentinel
298
- * returns every connected socket everywhere.
299
- *
300
- * Throws when no Socket.io server is registered: a silent `[]` is precisely the
301
- * failure class this helper exists to remove.
302
- */
303
- declare const getRoomSockets: (room: string, options?: GetRoomSocketsOptions) => Promise<RoomSocket[]>;
304
-
305
- type RuntimeMapRecord = Record<string, unknown>;
306
- interface RuntimeApiMapsResult {
307
- apisObject: RuntimeMapRecord;
308
- functionsObject: RuntimeMapRecord;
309
- }
310
- interface RuntimeSyncMapsResult {
311
- syncObject: RuntimeMapRecord;
312
- functionsObject: RuntimeMapRecord;
313
- }
314
- interface RuntimeMapsProvider {
315
- getRuntimeApiMaps: () => Promise<RuntimeApiMapsResult>;
316
- getRuntimeSyncMaps: () => Promise<RuntimeSyncMapsResult>;
317
- }
318
- declare const registerRuntimeMapsProvider: (provider: RuntimeMapsProvider) => void;
319
- declare const isRuntimeMapsProviderRegistered: () => boolean;
320
- declare const getRuntimeApiMaps: () => Promise<RuntimeApiMapsResult>;
321
- declare const getRuntimeSyncMaps: () => Promise<RuntimeSyncMapsResult>;
322
-
323
- /**
324
- * A single email attachment (email F2). Adapters (Resend, SMTP/Nodemailer, SES)
325
- * thread these into their provider payload. `content` is the raw bytes
326
- * (`Buffer`/`Uint8Array`) or a base64 string; provide a `path`/`href` instead
327
- * for adapters that fetch externally. Shapes are intentionally permissive so a
328
- * single attachment object works across providers — each adapter maps the
329
- * fields it supports.
330
- */
331
- interface EmailAttachment {
332
- /** File name shown to the recipient (e.g. `invoice.pdf`). */
333
- filename: string;
334
- /** Inline bytes or base64 string. Mutually exclusive with `path`/`href`. */
335
- content?: Buffer | Uint8Array | string;
336
- /**
337
- * Local file path the adapter reads (when it supports path-based sends).
338
- *
339
- * @security Trusted-input field — do NOT derive from client-supplied data
340
- * without validation. Framework-level path-traversal or safe-directory checks
341
- * are NOT applied automatically; callers must reject paths that escape the
342
- * intended directory (e.g. containing `..` or absolute paths outside a known
343
- * root) before passing them here.
344
- */
345
- path?: string;
346
- /**
347
- * Remote URL the adapter fetches (when it supports href-based sends).
348
- *
349
- * @security Trusted-input field — do NOT derive from client-supplied data
350
- * without validation. Only `https:` and `http:` URLs are safe for
351
- * framework-internal use; arbitrary schemes (e.g. `file:`, `data:`) may be
352
- * forwarded to nodemailer/Resend without restriction. Validate against an
353
- * allowlist before passing here.
354
- */
355
- href?: string;
356
- /** MIME type (e.g. `application/pdf`). Adapter may infer from `filename` if omitted. */
357
- contentType?: string;
358
- /** `Content-ID` for inline/embedded images referenced by `cid:` in the HTML. */
359
- cid?: string;
360
- }
361
- interface EmailMessage {
362
- to: string | string[];
363
- subject: string;
364
- html: string;
365
- text?: string;
366
- from?: string;
367
- replyTo?: string;
368
- cc?: string | string[];
369
- bcc?: string | string[];
370
- /**
371
- * File attachments (email F2). Owned by core (not `@luckystack/email`) so the
372
- * type is the single source of truth across the framework and every adapter;
373
- * adapters thread these into their provider's attachment payload. Optional —
374
- * a message without attachments behaves exactly as before.
375
- */
376
- attachments?: EmailAttachment[];
377
- /**
378
- * Custom message headers (email F2) — e.g. `X-Entity-Ref-ID`,
379
- * `List-Unsubscribe`, idempotency keys. Adapters merge these over the headers
380
- * they set themselves (adapter-reserved headers win to avoid breaking
381
- * delivery). Optional.
382
- *
383
- * @security CR and LF characters in header keys or values enable SMTP header
384
- * injection. `sendEmail` strips `\r` and `\n` from every key and value before
385
- * passing them to the adapter (EMAIL-O7). Do NOT derive keys or values from
386
- * unsanitized client input.
387
- */
388
- headers?: Record<string, string>;
389
- }
390
- type EmailDeliveryOutcome = 'not-sent' | 'unknown';
391
- type EmailResult = {
392
- ok: true;
393
- id: string;
394
- } | {
395
- ok: false;
396
- reason: string;
397
- cause?: unknown;
398
- /**
399
- * `unknown` means the caller stopped waiting after dispatch began; retry
400
- * only with the same idempotency key. Omitted on legacy/provider failures
401
- * whose delivery semantics are adapter-specific.
402
- */
403
- deliveryOutcome?: EmailDeliveryOutcome;
404
- };
405
- interface EmailSendContext {
406
- /** Cooperative cancellation. Custom adapters should abort provider I/O when supported. */
407
- signal: AbortSignal;
408
- /** Stable caller-provided key reused across retries of the same logical email. */
409
- idempotencyKey?: string;
410
- }
411
- interface EmailSender {
412
- /** Adapter identifier for logs + diagnostics ("console", "resend", "smtp", etc.). */
413
- name: string;
414
- /**
415
- * The optional context preserves source compatibility with pre-0.7 adapters.
416
- * New adapters should honor `signal` and provider-native idempotency where available.
417
- */
418
- send: (message: EmailMessage, context?: EmailSendContext) => Promise<EmailResult>;
419
- }
420
- /**
421
- * Registry keyed by adapter-slot name. Reserved slots used by the
422
- * framework:
423
- *
424
- * - `'default'` — fallback when no specific slot matches.
425
- * - `'transactional'` — login password-reset, account-confirmation,
426
- * invoice-receipt, security-critical emails.
427
- * - `'marketing'` — newsletters, announcements; consumers MAY route
428
- * bulk sends here.
429
- * - `'diagnostics'` — diagnostic-test endpoints (e.g. playground).
430
- *
431
- * Custom slot names ('billing', 'support', etc.) are allowed; consumers
432
- * resolve them explicitly via `sendEmail({ adapter: 'billing', ... })`.
433
- */
434
- type EmailSenderRegistry = Partial<Record<string, EmailSender>>;
435
- /**
436
- * Register a single global email sender. Kept for backwards compatibility.
437
- * For new code, prefer `registerEmailSenders({ ... })` which lets the
438
- * framework route transactional vs. marketing through different adapters.
439
- */
440
- declare const registerEmailSender: (sender: EmailSender) => void;
441
- /**
442
- * Register multiple named email senders. Replaces the entire registry
443
- * (last-write-wins). Use this when your app routes different message
444
- * types through different adapters — e.g. Resend for marketing and SMTP
445
- * for transactional. The framework picks adapter slots by convention
446
- * (see `EmailSenderRegistry`); consumer code can pick explicitly via
447
- * `sendEmail({ adapter: 'foo', ... })`.
448
- */
449
- declare const registerEmailSenders: (senders: EmailSenderRegistry) => void;
450
- /** Read the legacy single sender (or the `default` slot of the registry). */
451
- declare const getEmailSender: () => EmailSender | null;
452
- /**
453
- * Read a specific named sender. Falls back to the legacy single sender
454
- * when the requested slot is absent. Returns null when neither exists.
455
- * Pass the convention-based slots (`'transactional'`, `'marketing'`,
456
- * `'diagnostics'`, `'default'`) or any custom slot you registered.
457
- */
458
- declare const getEmailSenderByName: (name: string) => EmailSender | null;
459
- /** Read every registered slot name. */
460
- declare const listEmailSenderNames: () => string[];
461
- declare const isEmailSenderRegistered: () => boolean;
462
-
463
- type LocaleReloader = () => void | Promise<void>;
464
- declare const registerLocaleReloader: (reloader: LocaleReloader) => LocaleReloader;
465
- declare const getLocaleReloader: () => LocaleReloader | null;
466
-
467
- interface SentryInstance {
468
- captureException: (exception: unknown, ...args: unknown[]) => string;
469
- captureMessage: (message: string, ...args: unknown[]) => string;
470
- setUser: (user: unknown) => void;
471
- setContext: (key: string, context: unknown) => void;
472
- startInactiveSpan?: (context: unknown) => unknown;
473
- }
474
- /**
475
- * @deprecated Use `registerErrorTracker` from `@luckystack/error-tracking` instead.
476
- * Will be removed in a future major version.
477
- */
478
- declare const initSharedSentry: (instance: SentryInstance) => void;
479
- declare const captureException: (error: unknown, context?: Record<string, unknown>) => void;
480
- declare const captureMessage: (message: string, level?: "info" | "warning" | "error" | "fatal", context?: Record<string, unknown>) => void;
481
- declare const setSentryUser: (user: {
482
- id?: string;
483
- email?: string;
484
- username?: string;
485
- } | null) => void;
486
- declare const startSpan: (name: string, op: string) => unknown;
487
-
488
- interface ErrorTrackerUser {
489
- id?: string;
490
- email?: string;
491
- username?: string;
492
- [key: string]: unknown;
493
- }
494
- /** Open an isolated request identity scope before the first async boundary. */
495
- declare const runWithErrorTrackerIdentityScope: <T>(fn: () => T) => T;
496
- /** Open an isolated scope with a user that is already known. */
497
- declare const runWithErrorTrackerIdentity: <T>(user: ErrorTrackerUser | null, fn: () => T) => T;
498
- /** Set the user inside the active request scope; no-op outside a scope. */
499
- declare const setCurrentErrorTrackerIdentity: (user: ErrorTrackerUser | null) => void;
500
- /** Read the current request identity, or null outside a scope. */
501
- declare const getCurrentErrorTrackerIdentity: () => ErrorTrackerUser | null;
502
-
503
- type ErrorTrackerContext = Record<string, unknown>;
504
- declare const sanitizeErrorString: (value: string) => string;
505
- declare const sanitizeErrorStrings: (error: unknown) => {
506
- message: string;
507
- stack: string | undefined;
508
- } | null;
509
- interface ErrorTrackerEvent {
510
- /** When false, the adapter must not forward this event (beforeSend opt-out). */
511
- forwarded: boolean;
512
- kind: 'exception' | 'message';
513
- payload: Record<string, unknown>;
514
- }
515
- type SpanResult<T> = T;
516
- interface ErrorTracker {
517
- /** Human-readable identifier (logs, diagnostics). */
518
- name: string;
519
- captureException: (error: unknown, context?: ErrorTrackerContext) => void;
520
- captureMessage: (message: string, level: 'info' | 'warning' | 'error' | 'fatal', context?: ErrorTrackerContext) => void;
521
- setUser: (user: ErrorTrackerUser | null) => void;
522
- setContext?: (key: string, context: ErrorTrackerContext | null) => void;
523
- startSpan?: <T>(name: string, op: string, fn: () => T) => T;
524
- /**
525
- * Optional handle-style span (#62): open a span NOW and close it later via the
526
- * returned handle's `finish()`. Unlike {@link startSpan} (which wraps a single
527
- * synchronous `fn`), this fits an open-at-preExecute / close-at-postExecute
528
- * request lifecycle that spans awaits. When present, the registry-level
529
- * {@link startSpanHandle} delegates to the FIRST registered adapter that
530
- * implements it so the request span reaches an adapter-only backend
531
- * (Datadog/PostHog/custom), not just the legacy Sentry SDK path.
532
- */
533
- startSpanHandle?: (name: string, op: string) => {
534
- finish: () => void;
535
- };
536
- recordMetric?: (name: string, value: number, tags?: Record<string, string>) => void;
537
- beforeSend?: (event: ErrorTrackerEvent) => ErrorTrackerEvent | null;
538
- /**
539
- * Optional flush lifecycle hook (error-tracking ET batch). Called by
540
- * {@link flushErrorTrackers} on graceful shutdown so a buffered adapter
541
- * (PostHog batch, Sentry transport) can drain in-flight events before exit.
542
- * Returns a promise that resolves when the adapter has flushed.
543
- */
544
- flush?: () => Promise<void>;
545
- }
546
- type PreCaptureFilter = (event: ErrorTrackerEvent) => boolean;
547
- declare const registerPreCaptureFilter: (filter: PreCaptureFilter | null) => void;
548
- declare const registerErrorTracker: (tracker: ErrorTracker) => void;
549
- declare const registerErrorTrackers: (trackers: ErrorTracker[]) => void;
550
- /**
551
- * APPEND primitive (ET-05): add a tracker WITHOUT clobbering already-registered
552
- * ones. Fixes the async-PostHog-vs-consumer-overlay race where the loser of
553
- * `registerErrorTracker`'s replace silently vanished. De-duplicates by `name`
554
- * (re-appending the same adapter name replaces that one entry in place) so a
555
- * lazy adapter that registers a proxy then the real client doesn't double-fire.
556
- */
557
- declare const appendErrorTracker: (tracker: ErrorTracker) => void;
558
- declare const getActiveErrorTrackers: () => ErrorTracker[];
559
- /**
560
- * Flush lifecycle (error-tracking ET batch): drain every adapter that exposes a
561
- * `flush()` before shutdown. Per-adapter failures are swallowed + logged so one
562
- * stuck adapter can't block graceful shutdown. Safe to call when no adapter has
563
- * `flush`.
564
- */
565
- declare const flushErrorTrackers: () => Promise<void>;
566
- declare const captureExceptionAcrossTrackers: (error: unknown, context?: ErrorTrackerContext) => void;
567
- declare const captureMessageAcrossTrackers: (message: string, level: "info" | "warning" | "error" | "fatal", context?: ErrorTrackerContext) => void;
568
- declare const setErrorTrackerUser: (user: ErrorTrackerUser | null) => void;
569
- declare const recordMetricAcrossTrackers: (name: string, value: number, tags?: Record<string, string>) => void;
570
- declare const startSpanAcrossTrackers: <T>(name: string, op: string, fn: () => T) => T;
571
- /**
572
- * Handle-style span (error-tracking ET batch): for callers that can't wrap the
573
- * measured work in a single synchronous `fn` (streaming, cross-await spans).
574
- * Returns a handle whose `finish()` resolves the span's duration. The default
575
- * implementation is a lightweight wall-clock timer (works with any backend);
576
- * an adapter can supply a richer span via the existing `startSpan` callback
577
- * form when it needs native instrumentation. `finish()` is idempotent.
578
- */
579
- interface SpanHandle {
580
- /** Stop the span; safe to call more than once (subsequent calls are no-ops). */
581
- finish: () => void;
582
- /** Milliseconds elapsed at the moment `finish()` first ran (0 before). */
583
- readonly durationMs: number;
584
- }
585
- declare const startSpanHandle: (name: string, op: string) => SpanHandle;
586
-
587
- declare const EnvSchema: z.ZodObject<{
588
- NODE_ENV: z.ZodDefault<z.ZodEnum<{
589
- development: "development";
590
- production: "production";
591
- test: "test";
592
- }>>;
593
- SERVER_IP: z.ZodDefault<z.ZodString>;
594
- SECURE: z.ZodDefault<z.ZodEnum<{
595
- true: "true";
596
- false: "false";
597
- }>>;
598
- REDIS_HOST: z.ZodDefault<z.ZodString>;
599
- REDIS_PORT: z.ZodDefault<z.ZodString>;
600
- PROJECT_NAME: z.ZodDefault<z.ZodString>;
601
- }, z.core.$loose>;
602
- type RuntimeEnv = z.infer<typeof EnvSchema>;
603
- declare const DEFAULT_ENV_FILES: string[];
604
- declare const getEnvFiles: () => string[];
605
- declare const loadEnvFiles: () => void;
606
- declare const bootstrapEnv: () => RuntimeEnv;
607
- declare const env: {
608
- [x: string]: unknown;
609
- NODE_ENV: "development" | "production" | "test";
610
- SERVER_IP: string;
611
- SECURE: "true" | "false";
612
- REDIS_HOST: string;
613
- REDIS_PORT: string;
614
- PROJECT_NAME: string;
615
- };
616
- declare const isProduction: boolean;
617
- declare const getEnv: () => RuntimeEnv;
618
- /** Application runtime mode. Keep separate from `resolveEnvKey()`, which is a
619
- * deploy-topology identity and may be `staging`, `dockerSplit`, etc. */
620
- declare const resolveRuntimeMode: () => RuntimeEnv["NODE_ENV"];
621
- declare const isProductionRuntime: () => boolean;
622
- declare const isTestRuntime: () => boolean;
623
-
624
- declare global {
625
- var __luckystackPrisma: PrismaClient | undefined;
626
- }
627
- /**
628
- * Is a generated `@prisma/client` present in this install? Used by the
629
- * default resolver's error path and by `/readyz` to decide whether a missing
630
- * DB ping means "broken" or "this project has no Prisma by design".
631
- */
632
- declare const isPrismaClientResolvable: () => boolean;
633
- declare const prisma: PrismaClient<_prisma_client.Prisma.PrismaClientOptions, never, _prisma_client_runtime_library.DefaultArgs>;
634
-
635
- type DbHealthResult = boolean | 'skipped';
636
- type DbHealthCheck = () => Promise<DbHealthResult> | DbHealthResult;
637
- declare const registerDbHealthCheck: (check: DbHealthCheck) => void;
638
- declare const getDbHealthCheck: () => DbHealthCheck | null;
639
- declare const isDbHealthCheckRegistered: () => boolean;
640
- declare const resetDbHealthCheckForTests: () => void;
641
-
642
- declare const resetDefaultRedisClient: () => void;
643
- declare const rebuildDefaultRedisClient: () => Redis;
644
- interface RedisConnectionOptions {
645
- host: string;
646
- port: number;
647
- username?: string;
648
- password?: string;
649
- }
650
- declare const getRedisConnectionOptions: () => RedisConnectionOptions;
651
- declare const registerStrayPrefixCommand: (...commands: string[]) => void;
652
- declare const redis: Redis$1;
653
-
654
- type RedisKeyFormatter = (namespace: string, suffix: string) => string;
655
- declare const defaultRedisKeyFormatter: RedisKeyFormatter;
656
- declare const registerRedisKeyFormatter: (fn: RedisKeyFormatter) => void;
657
- declare const getRedisKeyFormatter: () => RedisKeyFormatter;
658
- declare const resetRedisKeyFormatterForTests: () => void;
659
- declare const formatKey: (namespace: string, suffix?: string) => string;
660
- declare const applyStrayKeyPrefix: (key: string) => string;
661
-
662
- /**
663
- * Acquire an exclusive lease named `name` for `ttlMs` milliseconds. Returns an
664
- * opaque owner token on success, or `null` when another owner already holds it.
665
- * `SET key token PX ttl NX` is atomic, so concurrent acquirers race safely.
666
- * Keep the returned token to `renewLease` / `releaseLease`.
667
- */
668
- declare const acquireLease: (name: string, ttlMs: number) => Promise<string | null>;
669
- /**
670
- * Extend a held lease by `ttlMs` (sliding ownership). Returns true when the
671
- * supplied `token` still owns the lease and the TTL was applied, false
672
- * otherwise (lost ownership, expired, or Redis error).
673
- */
674
- declare const renewLease: (name: string, token: string, ttlMs: number) => Promise<boolean>;
675
- /**
676
- * Release a held lease. Owner-checked: only succeeds (returns true) when
677
- * `token` is the current holder. Returns false on mismatch or Redis error.
678
- */
679
- declare const releaseLease: (name: string, token: string) => Promise<boolean>;
680
-
681
- declare const DEFAULT_CLIENT_KEY = "default";
682
- declare const registerPrismaClient: (client: PrismaClient, key?: string) => PrismaClient;
683
- declare const registerRedisClient: (client: Redis, key?: string) => Redis;
684
- declare const isPrismaClientRegistered: () => boolean;
685
- declare const isRedisClientRegistered: () => boolean;
686
- declare const getPrismaClientKeys: () => string[];
687
- declare const getRedisClientKeys: () => string[];
688
- declare const getPrismaClientFor: (key?: string) => PrismaClient;
689
- declare const getRedisClientFor: (key?: string) => Redis;
690
- declare const getPrismaClient: () => PrismaClient;
691
- declare const getRedisClient: () => Redis;
692
- declare const resetClientsForTests: () => void;
693
-
694
- interface AttachSocketRedisAdapterOptions {
695
- /**
696
- * Options forwarded to `@socket.io/redis-adapter`'s `createAdapter`
697
- * (e.g. `key` prefix, `requestsTimeout`, `publishOnSpecificResponseChannel`).
698
- * `key` defaults to {@link resolveSocketAdapterKey} when omitted.
699
- */
700
- adapterOptions?: Partial<RedisAdapterOptions>;
701
- /**
702
- * Supply a pre-built pub client instead of `redis.duplicate()`. Use this for
703
- * a dedicated adapter connection, custom auth, or a sentinel/cluster handle.
704
- * When omitted the framework duplicates the default `redis` handle.
705
- */
706
- pubClient?: Redis;
707
- /** Pre-built sub client (see `pubClient`). */
708
- subClient?: Redis;
709
- }
710
- declare const resolveSocketAdapterKey: () => string;
711
- declare const attachSocketRedisAdapter: (io: Server, options?: AttachSocketRedisAdapterOptions) => void;
712
-
713
- declare const BOOT_KEY_PREFIX = "luckystack:boot:";
714
- declare const resolveEnvKey: () => string;
715
- declare const writeBootUuid: (envKey?: string) => Promise<string>;
716
- declare const readBootUuid: (envKey?: string) => Promise<string | null>;
717
- /**
718
- * Extends the current environment boot UUID without rotating its value.
719
- *
720
- * Every healthy backend may renew the environment-level key, so multi-instance
721
- * deployments do not depend on whichever process happened to write it last. If
722
- * Redis recovered after the key expired, the first refresher writes a new UUID.
723
- */
724
- declare const refreshBootUuid: (envKey?: string) => Promise<void>;
725
- interface BootUuidHeartbeat {
726
- stop: () => void;
727
- }
728
- /**
729
- * Keeps the Redis boot UUID alive for as long as this backend is running.
730
- *
731
- * The self-rescheduling timeout waits for each Redis operation to settle before
732
- * scheduling another, so an outage cannot accumulate overlapping refreshes.
733
- * The timer is unref'd and therefore never keeps an otherwise-finished process
734
- * alive. Failures are logged and retried on the next interval.
735
- */
736
- declare const startBootUuidHeartbeat: (envKey?: string) => BootUuidHeartbeat;
737
-
738
- /**
739
- * Hash a synchronized value under an EXPLICIT `{ mode, salt }` (SEC-13). The
740
- * shared primitive the router calls with the same config the backend used, so
741
- * the cross-env compare still holds:
742
- * - `'plain'` → `sha256(value)` (today's behavior; `salt` ignored).
743
- * - `'salted'` → `sha256(salt + value)`.
744
- * - `'hmac'` → `HMAC-SHA256(key=salt, value)`.
745
- * A `'salted'`/`'hmac'` mode with an empty resolved salt falls back to `'plain'`
746
- * (and is the caller's responsibility to avoid) rather than silently hashing
747
- * with an empty key.
748
- */
749
- declare const hashSynchronizedValueWith: (config: Pick<HealthHashConfig, "mode"> & {
750
- salt: string;
751
- }, value: string) => string;
752
- /**
753
- * Resolve the effective health-hash config from `http.healthHash`, expanding the
754
- * `'@bootUuid'` salt sentinel to the supplied boot UUID. Honoured for BOTH
755
- * `'salted'` and `'hmac'` (the 0.2.0 default is `hmac` + `'@bootUuid'`, so the
756
- * per-boot UUID becomes the HMAC key — the synchronized-env fingerprint rotates
757
- * every restart instead of being a stable, dictionary-attackable `sha256`). The
758
- * server passes its current boot UUID; the router, comparing, passes the same
759
- * value it read from the backend's handshake. When the sentinel is in play but
760
- * no boot UUID is available the config collapses to `'plain'` so the cross-env
761
- * compare never silently diverges.
762
- */
763
- declare const resolveHealthHashConfig: (bootUuid?: string | null) => {
764
- mode: HealthHashConfig["mode"];
765
- salt: string;
766
- };
767
- /**
768
- * Safe-to-expose descriptor of the resolved health-hash config. The router needs
769
- * to hash with the SAME `{ mode, salt }` the backend used, but a static `salt`
770
- * (mode `'salted'`/`'hmac'` with a non-sentinel salt) is a SECRET and must never
771
- * leave the backend. So `/_health` exposes only the `mode` + whether the salt is
772
- * the `'@bootUuid'` sentinel (the bootUuid itself is already in the response).
773
- * From this the router can reproduce the hash for `'plain'` and `'@bootUuid'`
774
- * configs, and KNOWS it cannot reproduce a static-salt config (so it skips the
775
- * compare instead of reporting a false drift).
776
- */
777
- interface HealthHashDescriptor {
778
- mode: HealthHashConfig['mode'];
779
- bootUuidSalt: boolean;
780
- }
781
- /** Describe the active health-hash config for the `/_health` payload (no secret salt). */
782
- declare const describeHealthHashConfig: () => HealthHashDescriptor;
783
- /**
784
- * Reproduce the backend's resolved `{ mode, salt }` from its `/_health`
785
- * descriptor + the shared boot UUID. Returns `null` when the backend uses a
786
- * STATIC salt the router cannot see — the caller must then skip the compare
787
- * rather than hash with a different (default) config and report a false drift.
788
- */
789
- declare const resolveHealthHashConfigFromDescriptor: (descriptor: HealthHashDescriptor, bootUuid: string) => {
790
- mode: HealthHashConfig["mode"];
791
- salt: string;
792
- } | null;
793
- declare const collectSynchronizedEnvKeys: () => string[];
794
- /**
795
- * Compute the `/_health` hash map for every synchronized env var. Reads
796
- * `http.healthHash` (call-time) so a consumer's salt/HMAC opt-in applies;
797
- * `bootUuid` is consulted only when the salt is the `'@bootUuid'` sentinel.
798
- * The 0.2.0 DEFAULT (`{ mode: 'hmac', salt: '@bootUuid' }`) HMAC-keys the output
799
- * on `bootUuid`; pass `mode: 'plain'` to restore the historical unsalted output.
800
- */
801
- declare const computeSynchronizedEnvHashes: (bootUuid?: string | null) => Record<string, string | null>;
802
- declare const hashSynchronizedValue: (value: string, bootUuid?: string | null) => string;
803
-
804
- declare const initConsolelog: () => void;
805
-
806
- declare const DEFAULT_REDACTED_LOG_KEYS: readonly string[];
807
- declare const registerRedactedLogKeys: (keys: readonly string[]) => void;
808
- declare const getRedactedLogKeys: () => readonly string[];
809
- declare const isRedactedLogKey: (key: string) => boolean;
810
- declare const resetRedactedLogKeysForTests: () => void;
811
- /** Placeholder substituted for a redacted value. */
812
- declare const REDACTED_PLACEHOLDER = "[redacted]";
813
- declare const DEPTH_TRUNCATED_PLACEHOLDER = "[truncated: max depth]";
814
- /**
815
- * Recursively deep-copy `value`, replacing any property whose key matches the
816
- * registered redacted-keys set (case-insensitive) with {@link REDACTED_PLACEHOLDER}
817
- * (SYNC-17 defense-in-depth). Use it at raw log / `captureException` context
818
- * sites so a token/password nested anywhere in a payload never lands verbatim in
819
- * a logger sink or error-tracker breadcrumb.
820
- *
821
- * - Primitives pass through unchanged.
822
- * - Arrays are mapped element-wise (array indices are never "keys", so an
823
- * element is only redacted by its own nested object keys).
824
- * - Cycles / over-deep graphs are cut at {@link MAX_SANITIZE_DEPTH} with
825
- * {@link DEPTH_TRUNCATED_PLACEHOLDER}; the input is never mutated.
826
- */
827
- declare const sanitizeForLog: (value: unknown, depth?: number, _seen?: WeakSet<object>) => unknown;
828
-
829
- declare const getCookieValue: (cookieHeader: string | undefined, cookieName: string) => string | null;
830
- declare const hasCookie: (cookieHeader: string | undefined, cookieName: string) => boolean;
831
- /** Attributes a cookie-builder must honour, after applying any name prefix. */
832
- interface CookiePrefixConstraints {
833
- /** Effective cookie name (prefix prepended when applicable). */
834
- name: string;
835
- /** Whether `Secure` must be set (forced true by either prefix). */
836
- secure: boolean;
837
- /** Whether `Path=/` is forced (true under `__Host-`). */
838
- forcePathRoot: boolean;
839
- /** Whether a `Domain` attribute is FORBIDDEN (true under `__Host-`). */
840
- forbidDomain: boolean;
841
- }
842
- /**
843
- * Compute the attribute constraints a `__Host-` / `__Secure-` cookie-name prefix
844
- * imposes (CORE-10/39), so the server's session + CSRF cookie builders apply the
845
- * browser-enforced rules consistently:
846
- *
847
- * - `'__Host-'` → name prefixed, `Secure` forced, `Path=/` forced, `Domain` forbidden.
848
- * - `'__Secure-'` → name prefixed, `Secure` forced.
849
- * - `undefined` → name unchanged, no forced attributes (today's behavior).
850
- *
851
- * Pure + deterministic — the builder still owns serialization; this only
852
- * resolves the constraints so server and any other cookie-site can't drift.
853
- * `secureOverride` lets the caller force `Secure` independent of a prefix
854
- * (mirrors `http.sessionCookieSecure`).
855
- */
856
- declare const applyCookiePrefixConstraints: (baseName: string, prefix: "__Host-" | "__Secure-" | undefined, secureOverride?: boolean) => CookiePrefixConstraints;
857
-
858
- /**
859
- * HTTP API Utilities
860
- *
861
- * Helpers for HTTP API request handling including:
862
- * - Smart HTTP method detection based on API name
863
- * - Method validation
864
- */
865
- type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
866
- /**
867
- * Single source of truth for the GET-prefix naming heuristic
868
- * (`get` / `fetch` / `list`). Used by both `inferHttpMethod` here and the
869
- * client transport's `isGetMethod` fallback in `apiRequest.ts`, so the two
870
- * prefix heuristics can't drift apart. Expects an already-lowercased leaf name.
871
- */
872
- declare const isGetMethodName: (methodNameLower: string) => boolean;
873
- /**
874
- * Infer the HTTP method from an API name based on common naming conventions.
875
- *
876
- * - Names starting with "get" → GET
877
- * - Names starting with "delete" or "remove" → DELETE
878
- * - Names starting with "update" or "edit" → PUT
879
- * - Everything else → POST (create operations, actions)
880
- *
881
- * @example
882
- * ```typescript
883
- * inferHttpMethod('getUserData') // → 'GET'
884
- * inferHttpMethod('deleteAccount') // → 'DELETE'
885
- * inferHttpMethod('updateProfile') // → 'PUT'
886
- * inferHttpMethod('createUser') // → 'POST'
887
- * inferHttpMethod('sendEmail') // → 'POST'
888
- * ```
889
- */
890
- declare const inferHttpMethod: (apiName: string) => HttpMethod;
891
- /**
892
- * Get the effective HTTP method for an API.
893
- * Uses explicit export if available, otherwise infers from name.
894
- */
895
- declare const getEffectiveHttpMethod: (apiConfig: {
896
- httpMethod?: HttpMethod;
897
- }, apiName: string) => HttpMethod;
898
- /**
899
- * Check if an HTTP method is valid for an API.
900
- *
901
- * @param requestMethod - The method from the HTTP request
902
- * @param allowedMethod - The method the API accepts
903
- * @returns true only when the method matches the route's allowed method.
904
- *
905
- * NOTE: `OPTIONS` is intentionally NOT treated as allowed here. A custom-route
906
- * author wiring this into their dispatch must answer CORS preflights BEFORE the
907
- * route check — letting `OPTIONS` through would execute the handler on a request
908
- * the CSRF middleware treats as non-state-changing (CSRF-exempt), opening a
909
- * bypass on method-locked routes.
910
- */
911
- declare const isMethodAllowed: (requestMethod: string, allowedMethod: HttpMethod) => boolean;
912
-
913
- declare const ROOT_DIR: string;
914
- declare const getSrcDir: () => string;
915
- declare const getServerDir: () => string;
916
- declare const getSharedDir: () => string;
917
- declare const getUploadsDir: () => string;
918
- declare const getPublicDir: () => string;
919
- declare const getServerFunctionDirs: () => string[];
920
- /**
921
- * @deprecated Use `getServerFunctionDirs()` instead. Returns the first
922
- * directory from the array, or `'functions'` resolved against the workspace
923
- * root when no directories are configured.
924
- */
925
- declare const getServerFunctionsDir: () => string;
926
- declare const getGeneratedSocketTypesPath: () => string;
927
- declare const getGeneratedApiSchemasPath: () => string;
928
- declare const getGeneratedApiDocsPath: () => string;
929
- declare const SRC_DIR: string;
930
- /** @deprecated use `getServerDir()` instead. */
931
- declare const SERVER_DIR: string;
932
- /** @deprecated use `getSharedDir()` instead. */
933
- declare const SHARED_DIR: string;
934
- /** @deprecated use `getUploadsDir()` instead. */
935
- declare const UPLOADS_DIR: string;
936
- /** @deprecated use `getPublicDir()` instead. */
937
- declare const PUBLIC_DIR: string;
938
- /** @deprecated use `getServerFunctionsDir()` instead. */
939
- declare const SERVER_FUNCTIONS_DIR: string;
940
- /** @deprecated use `getGeneratedSocketTypesPath()` instead. */
941
- declare const GENERATED_SOCKET_TYPES_PATH: string;
942
- /** @deprecated use `getGeneratedApiSchemasPath()` instead. */
943
- declare const GENERATED_API_SCHEMAS_PATH: string;
944
- /** @deprecated use `getGeneratedApiDocsPath()` instead. */
945
- declare const GENERATED_API_DOCS_PATH: string;
946
- declare const TSCONFIG_ALIAS_FILES: string[];
947
- declare const resolveFromRoot: (...segments: string[]) => string;
948
-
949
- type SocketMiddleware = (socket: Socket, next: (err?: Error) => void) => void;
950
- /**
951
- * Register a Socket.io middleware that will be wired via `io.use(...)` when
952
- * the framework boots the socket server. Multiple middlewares can be
953
- * registered — they run in registration order before any `connect`
954
- * handler fires.
955
- */
956
- declare const registerSocketMiddleware: (mw: SocketMiddleware) => void;
957
- /** Read the registered middleware list (read-only view). */
958
- declare const getSocketMiddlewares: () => readonly SocketMiddleware[];
959
- /** Test-only / hot-reload helper. Drops every registered middleware. */
960
- declare const clearSocketMiddlewares: () => void;
961
- /**
962
- * Wire every registered middleware into the running Socket.io server.
963
- * Called by `@luckystack/server`'s `loadSocket` after the `SocketIOServer`
964
- * is constructed and before any `connect` handler is attached, so custom
965
- * middlewares run before framework handlers see the socket.
966
- */
967
- declare const applySocketMiddlewares: (io: Server) => void;
968
-
969
- interface CsrfCookieOptions {
970
- sameSite?: 'strict' | 'lax' | 'none';
971
- secure?: boolean;
972
- /**
973
- * Whether the login-absent double-submit cookie is HttpOnly. The client gets
974
- * the matching header value from the same-origin `GET /auth/csrf` JSON body,
975
- * so either value works; `false` remains the compatibility default.
976
- */
977
- httpOnly?: boolean;
978
- path?: string;
979
- maxAgeMs?: number;
980
- }
981
- interface CsrfConfig {
982
- /** Cookie name used to deliver the CSRF token to the browser. */
983
- cookieName: string;
984
- /** Request-header name the client attaches on state-changing requests. */
985
- headerName: string;
986
- /** Length in bytes of the random token minted by the session layer. */
987
- tokenLength: number;
988
- cookieOptions: CsrfCookieOptions;
989
- }
990
- declare const DEFAULT_CSRF_CONFIG: CsrfConfig;
991
- /**
992
- * Override one or more CSRF settings. Merges shallowly with the current
993
- * config; `cookieOptions` is deep-merged so a partial override of (say)
994
- * `sameSite` does not clobber `path` / `maxAgeMs`. Last-write-wins.
995
- */
996
- declare const registerCsrfConfig: (input: Partial<CsrfConfig>) => void;
997
- /** Read the active config at call time (never at module load). */
998
- declare const getCsrfConfig: () => CsrfConfig;
999
- /** Test-only — restore defaults between scenarios. */
1000
- declare const resetCsrfConfigForTests: () => void;
1001
-
1002
- declare const serveAvatar: ({ routePath, res, }: {
1003
- routePath: string;
1004
- res: ServerResponse;
1005
- }) => Promise<void>;
1006
-
1007
- interface ProcessUploadInput {
1008
- /** User the upload is associated with. */
1009
- userId: string;
1010
- /** Content type from the source (e.g. parsed from a data URL prefix). */
1011
- contentType: string;
1012
- /** Raw upload bytes — used for `sizeBytes` and passed to the encoder. */
1013
- buffer: Buffer;
1014
- /** Identifier for the upload category. Defaults to `'avatar'`. */
1015
- uploadKind?: string;
1016
- /** Final file name written to disk (used in the `onUploadComplete` payload). */
1017
- fileName: string;
1018
- /**
1019
- * Project-supplied encode + write step. Receives the raw buffer, does
1020
- * whatever encoding/resizing the installer wants (sharp, ffmpeg, raw fs
1021
- * write, S3 upload, etc.), and returns the final byte size on disk —
1022
- * pass through `buffer.byteLength` if you don't have a meaningful
1023
- * post-encode size.
1024
- */
1025
- encodeAndSave: (buffer: Buffer) => Promise<number>;
1026
- }
1027
- type ProcessUploadResult = {
1028
- status: 'success';
1029
- sizeBytes: number;
1030
- } | {
1031
- status: 'rejected';
1032
- errorCode: string;
1033
- } | {
1034
- status: 'error';
1035
- reason: string;
1036
- cause?: unknown;
1037
- };
1038
- /**
1039
- * Run a project-side upload through the framework's hook contract.
1040
- *
1041
- * Order of operations:
1042
- * 1. Dispatch `onUploadStart` — handlers may return a stop signal to reject
1043
- * the upload (content moderation, virus scanner, quota check).
1044
- * 2. Run the project-supplied `encodeAndSave` callback (wrapped in tryCatch).
1045
- * 3. Dispatch `onUploadComplete` with the final `sizeBytes`.
1046
- *
1047
- * Returns one of three statuses so the caller can map to its own response
1048
- * envelope without inspecting hook internals.
1049
- */
1050
- declare const processUpload: (input: ProcessUploadInput) => Promise<ProcessUploadResult>;
1051
-
1052
- interface BindAddress {
1053
- ip: string;
1054
- port: number;
1055
- }
1056
- interface IntendedBindAddress extends BindAddress {
1057
- configuredPort?: number;
1058
- }
1059
- declare const registerBindAddress: (address: IntendedBindAddress) => void;
1060
- declare const registerBoundAddress: (address: BindAddress) => void;
1061
- declare const getBindAddress: () => {
1062
- ip: string;
1063
- port: string;
1064
- };
1065
- declare const resolveDevCallbackUrl: (callbackUrl: string) => string;
1066
-
1067
- interface AvatarConfig {
1068
- /**
1069
- * File format(s) the framework will look for on disk, in order. The first
1070
- * one that exists wins. Each entry is `{ extension, contentType }` so the
1071
- * `Content-Type` response header matches what the file actually is.
1072
- * Default: `[{ extension: 'webp', contentType: 'image/webp' }]`.
1073
- */
1074
- formats: {
1075
- extension: string;
1076
- contentType: string;
1077
- }[];
1078
- /**
1079
- * `Cache-Control` value sent with the avatar response.
1080
- * Default: `'public, max-age=86400'` (24 hours).
1081
- */
1082
- cacheControl: string;
1083
- }
1084
- declare const DEFAULT_AVATAR_CONFIG: AvatarConfig;
1085
- type AvatarConfigInput = DeepPartial<AvatarConfig>;
1086
- declare const registerAvatarConfig: (config: AvatarConfigInput) => void;
1087
- declare const getAvatarConfig: () => AvatarConfig;
1088
-
1089
- interface getParamsType {
1090
- method: string;
1091
- req: IncomingMessage;
1092
- res: ServerResponse;
1093
- queryString?: string;
1094
- }
1095
- declare function getParams({ method, req, res: _res, queryString }: getParamsType): Promise<Record<string, unknown> | null>;
1096
-
1097
- /**
1098
- * Extract the authentication token from a Socket.io connection.
1099
- *
1100
- * Checks `config.sessionBasedToken` to determine where to look:
1101
- * - `true`: Token is in `socket.handshake.auth.token` (sessionStorage on client)
1102
- * - `false`: Token is in cookies via `socket.handshake.headers.cookie`
1103
- *
1104
- * @param socket - The Socket.io socket instance
1105
- * @returns The token string or null if not found
1106
- */
1107
- declare const extractTokenFromSocket: (socket: Pick<Socket, "handshake">) => string | null;
1108
-
1109
- /**
1110
- * Extract the authentication token from an HTTP request.
1111
- *
1112
- * Supports tokens from:
1113
- * - `Authorization: Bearer <token>` header
1114
- * - the configured session cookie
1115
- *
1116
- * @param req - The HTTP incoming message
1117
- * @returns The token string or null if not found
1118
- */
1119
- declare const extractTokenFromRequest: (req: IncomingMessage) => string | null;
1120
-
1121
- /**
1122
- * Stable sentinel returned when no usable client address could be resolved.
1123
- * Kept as a single exported constant so every rate-limit key-site funnels
1124
- * missing addresses into one deterministic bucket instead of ad-hoc literals.
1125
- */
1126
- declare const UNKNOWN_CLIENT_IP = "unknown";
1127
- interface ResolveClientIpParams {
1128
- /**
1129
- * The raw transport peer address — `socket.handshake.address` (Socket.io)
1130
- * or `req.socket.remoteAddress` (raw HTTP). May be `undefined`/`null` when
1131
- * the connection has already been torn down.
1132
- */
1133
- rawAddress: string | null | undefined;
1134
- /** Request / handshake headers. Only consulted when `trustProxy` is true. */
1135
- headers: IncomingHttpHeaders;
1136
- /**
1137
- * Whether a known reverse proxy sits in front of this server. DEFAULT false.
1138
- * When false the raw peer address is returned verbatim (only IPv4-mapped
1139
- * IPv6 canonicalization is applied), preserving historical behaviour. Only
1140
- * enable when a trusted proxy populates `X-Forwarded-For` / `X-Real-IP`,
1141
- * otherwise a client can spoof its own IP via those headers.
1142
- */
1143
- trustProxy?: boolean;
1144
- /**
1145
- * Number of proxy hops to skip from the RIGHT of `X-Forwarded-For` when
1146
- * `trustProxy` is on (CORE-O3). The rightmost entries are appended by your own
1147
- * trusted proxies; the resolved client IP is the entry that many hops in from
1148
- * the end. The leftmost hop is client-controlled and is never trusted. DEFAULT
1149
- * 1 (the immediate upstream proxy). Clamped to the list length so an over-large
1150
- * count falls back to the leftmost trusted hop rather than the spoofable entry.
1151
- */
1152
- trustedProxyHopCount?: number;
1153
- }
1154
- /**
1155
- * Whether a resolved client IP is a loopback / unresolved address — used by the
1156
- * api/sync transports to honour `rateLimiting.skipLoopbackInDev` (api F5/F11).
1157
- * Matches IPv4 loopback (`127.0.0.0/8`), IPv6 loopback (`::1`), the IPv4-mapped
1158
- * form (`::ffff:127.0.0.1`, already canonicalized away by {@link resolveClientIp}),
1159
- * and the {@link UNKNOWN_CLIENT_IP} sentinel. Pass an already-resolved IP.
1160
- */
1161
- declare const isLoopbackIp: (ip: string) => boolean;
1162
- /**
1163
- * Derive the deterministic, non-reversible bucket component for a session
1164
- * token. The same token always yields the same value (bucket identity is
1165
- * preserved) while the raw token never appears in the returned string.
1166
- * Used by api AND sync rate-limit key builders — lives in core so both
1167
- * packages can share one implementation without a cross-package dependency.
1168
- */
1169
- declare const deriveTokenBucketId: (token: string) => string;
1170
- declare const resolveClientIp: ({ rawAddress, headers, trustProxy, trustedProxyHopCount }: ResolveClientIpParams) => string;
1171
-
1172
- interface SessionSaveResult {
1173
- ok: boolean;
1174
- errorCode?: string;
1175
- }
1176
- interface SessionLogoutInput {
1177
- token: string | null;
1178
- socket?: Socket;
1179
- userId?: string | null;
1180
- skipSessionDelete?: boolean;
1181
- }
1182
- interface SessionProvider {
1183
- getSession: (token: string | null) => Promise<BaseSessionLayout | null>;
1184
- saveSession: (token: string, data: BaseSessionLayout, newUser?: boolean, options?: {
1185
- supersedeToken?: string;
1186
- }) => Promise<SessionSaveResult>;
1187
- deleteSession: (token: string, options?: {
1188
- skipSocketLogout?: boolean;
1189
- }) => Promise<boolean>;
1190
- logout: (input: SessionLogoutInput) => Promise<void>;
1191
- }
1192
- declare const registerSessionProvider: (next: SessionProvider) => void;
1193
- declare const getRegisteredSessionProvider: () => SessionProvider | null;
1194
- declare const isSessionProviderRegistered: () => boolean;
1195
- declare const resetSessionProviderForTests: () => void;
1196
- declare const readSession: (token: string | null) => Promise<BaseSessionLayout | null>;
1197
- /**
1198
- * Persist a session via the registered provider, or no-op if no login provider
1199
- * is registered (unauthenticated-app case).
1200
- *
1201
- * **WARNING**: when no session provider is registered, this returns `{ ok: true }`
1202
- * as a no-op — the session is NOT actually persisted anywhere. Callers that
1203
- * check `result.ok` before considering a user logged in will incorrectly
1204
- * proceed as if the session was saved. This is intentional for apps that do not
1205
- * use `@luckystack/login`, but on a partial install (login package misconfigured
1206
- * or not yet registered) the failure is silent rather than loud.
1207
- * Use `isSessionProviderRegistered()` at boot to verify the provider is present.
1208
- */
1209
- declare const writeSession: (token: string, data: BaseSessionLayout, newUser?: boolean, options?: {
1210
- supersedeToken?: string;
1211
- }) => Promise<SessionSaveResult>;
1212
- declare const removeSession: (token: string, options?: {
1213
- skipSocketLogout?: boolean;
1214
- }) => Promise<boolean>;
1215
- declare const performLogout: (input: SessionLogoutInput) => Promise<void>;
1216
-
1217
- /**
1218
- * Shared validation utilities for API and Sync request handlers.
1219
- * Extracted to avoid code duplication between handleApiRequest and handleSyncRequest.
1220
- */
1221
-
1222
- /**
1223
- * Check if a value is falsy (false, 0, 0n, '', null, undefined, or NaN)
1224
- */
1225
- declare const isFalsy: (value: unknown) => boolean;
1226
- interface ValidationResult$1 {
1227
- status: 'success' | 'error';
1228
- errorCode?: string;
1229
- errorParams?: {
1230
- key: string;
1231
- value: string | number | boolean;
1232
- }[];
1233
- httpStatus?: number;
1234
- }
1235
- /**
1236
- * Validate a request against authentication requirements.
1237
- *
1238
- * Multiple constraints on a single `additional` entry are AND'd: every
1239
- * specified field (`nullish`, `type`, `value`, `mustBeFalsy`) must pass
1240
- * for that entry to validate. Each entry across the array is also AND'd:
1241
- * the request must pass every entry to succeed.
1242
- *
1243
- * Constraint semantics:
1244
- * - `nullish: true` — `val` must be `null` or `undefined`.
1245
- * - `nullish: false` — `val` must NOT be `null` or `undefined`.
1246
- * - `type: 'string'` — `typeof val === 'string'` (skipped when val is null/undefined).
1247
- * - `value: <x>` — strict equality `val === x`. Omit the field entirely if you
1248
- * don't want this check; setting `value: undefined`
1249
- * explicitly means "val must be undefined".
1250
- * - `mustBeFalsy: true` — `isFalsy(val)`.
1251
- * - `mustBeFalsy: false` — `!isFalsy(val)`.
1252
- *
1253
- * **SECURITY CONTRACT**: this function does NOT check `auth.login`. Login
1254
- * enforcement is the responsibility of the surrounding API/sync handler — it
1255
- * must verify the session exists before calling `validateRequest`. Callers
1256
- * that omit the login check will allow unauthenticated requests through the
1257
- * `additional[]` predicates even when `auth.login: true` is declared, because
1258
- * `validateRequest` short-circuits to success when `additional` is empty.
1259
- * The expected call pattern is:
1260
- * 1. Check session exists (enforce `auth.login`).
1261
- * 2. Call `validateRequest({ auth, user })` to enforce `auth.additional[]`.
1262
- */
1263
- declare const validateRequest: ({ auth, user }: {
1264
- auth: AuthProps;
1265
- user?: BaseSessionLayout | null;
1266
- }) => ValidationResult$1;
1267
-
1268
- /**
1269
- * Rate Limiter
1270
- *
1271
- * The framework's rate-limit dispatch is pluggable via
1272
- * `registerRateLimitStrategy()`. The built-in `defaultRateLimitStrategy`
1273
- * implements in-memory counters with optional Redis backing for
1274
- * multi-instance deployments. Consumers can plug in alternative strategies
1275
- * (token-bucket, sliding-window, per-user tiers, edge-KV-backed, etc.) by
1276
- * implementing the `RateLimitStrategy` interface.
1277
- */
1278
- interface CheckRateLimitParams {
1279
- /** Unique key for rate limiting (e.g., "user:123:api:getData") */
1280
- key: string;
1281
- /** Maximum requests allowed in window */
1282
- limit: number;
1283
- /** Time window in milliseconds (default: 60_000 = 1 minute) */
1284
- windowMs?: number;
1285
- }
1286
- interface RateLimitResult {
1287
- /** Whether the request is allowed */
1288
- allowed: boolean;
1289
- /** Remaining requests in current window */
1290
- remaining: number;
1291
- /** Seconds until rate limit resets */
1292
- resetIn: number;
1293
- }
1294
- /**
1295
- * Pluggable rate-limit backend. The framework calls these methods through
1296
- * the active strategy slot. Default implementation is memory + Redis.
1297
- * Replace via `registerRateLimitStrategy(...)` at boot.
1298
- */
1299
- interface RateLimitStrategy {
1300
- /** Human-readable identifier (used in logs/diagnostics). */
1301
- name: string;
1302
- /** Increment the counter for `key` and return whether the request is allowed. */
1303
- check(params: CheckRateLimitParams): Promise<RateLimitResult>;
1304
- /** Read current status without incrementing — useful for response headers. */
1305
- getStatus(key: string, limit: number): Promise<RateLimitResult>;
1306
- /** Reset the counter for a single key. */
1307
- clear(key: string): Promise<void>;
1308
- /** Reset all counters under this strategy's namespace. */
1309
- clearAll(): Promise<void>;
1310
- }
1311
- /**
1312
- * Built-in strategy: in-memory counters with optional Redis backing for
1313
- * multi-instance deployments. Mode is controlled by
1314
- * `projectConfig.rateLimiting.store` (`'memory'` or `'redis'`). When `redis`
1315
- * mode fails (e.g. Redis disconnect), the strategy silently degrades to
1316
- * in-memory and logs a one-shot warning.
1317
- */
1318
- declare const defaultRateLimitStrategy: RateLimitStrategy;
1319
- /**
1320
- * Replace the active rate-limit backend. Pass a strategy that implements
1321
- * `RateLimitStrategy`. Common use cases:
1322
- *
1323
- * - Token-bucket / sliding-window algorithms for smoother burst handling.
1324
- * - Per-user tier limits (premium accounts get higher caps).
1325
- * - Cloudflare-Workers-KV or upstash-rest-backed counters for edge.
1326
- * - No-op strategy for test environments.
1327
- *
1328
- * Call this once at boot (typically from `luckystack/server/index.ts`)
1329
- * BEFORE the first request lands. Subsequent calls overwrite the previous
1330
- * registration (last-write-wins).
1331
- */
1332
- declare const registerRateLimitStrategy: (strategy: RateLimitStrategy) => void;
1333
- /** Read the currently-active strategy (defaults to the built-in). */
1334
- declare const getRateLimitStrategy: () => RateLimitStrategy;
1335
- /**
1336
- * Check if a request is allowed under rate limiting rules.
1337
- * Increments the counter for the key if allowed.
1338
- */
1339
- declare const checkRateLimit: (params: CheckRateLimitParams) => Promise<RateLimitResult>;
1340
- /**
1341
- * Get current rate limit status without incrementing counter.
1342
- * Useful for rate limit headers in responses.
1343
- */
1344
- declare const getRateLimitStatus: (key: string, limit: number) => Promise<RateLimitResult>;
1345
- /**
1346
- * Clear rate limit for a specific key.
1347
- * Useful for admin overrides or testing.
1348
- */
1349
- declare const clearRateLimit: (key: string) => Promise<void>;
1350
- /**
1351
- * Clear all rate limits.
1352
- * Intended for testing or dev-mode server reset only.
1353
- * CORE-O8: throws in production to prevent accidental global counter wipe across
1354
- * all tenants. If you need a production-safe reset, pass a scoped key to
1355
- * `clearRateLimit` or implement a tenant-namespaced strategy.
1356
- */
1357
- declare const clearAllRateLimits: () => Promise<void>;
1358
-
1359
- type ValidationResult = {
1360
- status: 'success';
1361
- } | {
1362
- status: 'error';
1363
- message: string;
1364
- };
1365
- declare const validateInputByType: ({ typeText, value, rootKey, filePath, }: {
1366
- typeText?: string;
1367
- value: unknown;
1368
- rootKey: string;
1369
- filePath?: string;
1370
- }) => Promise<ValidationResult>;
1371
-
1372
- declare const normalizeOrigin: ({ value, secure }: {
1373
- value: string;
1374
- secure: boolean;
1375
- }) => string;
1376
- declare const allowedOrigin: (origin: string) => boolean;
1377
-
1378
- declare const registerSyncAbortController: (socketId: string, cb: string, controller: AbortController) => string;
1379
- declare const unregisterSyncAbortController: (key: string) => void;
1380
- declare const abortSyncByCb: (socketId: string, cb: string) => boolean;
1381
- declare const registerApiAbortController: (socketId: string, responseIndex: number | string, controller: AbortController) => string;
1382
- declare const unregisterApiAbortController: (key: string) => void;
1383
- declare const abortApiByResponseIndex: (socketId: string, responseIndex: number | string) => boolean;
1384
- declare const abortAllForSocket: (socketId: string) => void;
1385
-
1386
- declare const TEST_RESET_TARGETS: readonly ["rateLimits", "sessions", "activeUsers", "hooks"];
1387
- type TestResetTarget = typeof TEST_RESET_TARGETS[number];
1388
- /** Query parameter that limits a reset to the listed targets (comma-separated). */
1389
- declare const TEST_RESET_ONLY_PARAM = "only";
1390
- declare const isTestResetTarget: (value: string) => value is TestResetTarget;
1391
-
1392
- /**
1393
- * Handle returned by {@link issueOneTimeToken}. `token` is the RAW token to hand
1394
- * to the caller (embed in the emailed URL); call `store()` to persist its hash
1395
- * to Redis with the configured TTL. Persisting is a separate step so the caller
1396
- * can run veto logic (hooks, validation) between minting and committing without
1397
- * leaving an orphan Redis key.
1398
- */
1399
- interface OneTimeTokenHandle {
1400
- /** The raw, single-use token. Returned exactly once; never stored verbatim. */
1401
- token: string;
1402
- /** Persist `sha256(token)` -> `payload` in Redis with the issue TTL. */
1403
- store(): Promise<void>;
1404
- }
1405
- /**
1406
- * Mint a one-time token in `namespace` carrying `payload`, redeemable for `ttl`
1407
- * seconds. Returns the raw token plus a `store()` committer. The payload is
1408
- * serialised to a string (objects via JSON); on redemption it is returned as a
1409
- * string and the caller parses it (or use {@link consumeOneTimeTokenJson}).
1410
- *
1411
- * @param namespace Key namespace (e.g. `'-pwreset'`, `'-email-change'`) — routed
1412
- * through `formatKey` so the active key formatter / project prefix applies.
1413
- * @param ttlSeconds Time-to-live in seconds for the stored hash.
1414
- * @param payload Value bound to the token. A string is stored verbatim; any
1415
- * other value is `JSON.stringify`-d.
1416
- */
1417
- declare const issueOneTimeToken: (namespace: string, ttlSeconds: number, payload: string | Record<string, unknown>) => OneTimeTokenHandle;
1418
- /**
1419
- * Atomically validate + consume a one-time token from `namespace`. Returns the
1420
- * stored payload STRING on success (and deletes the entry, so it can never be
1421
- * reused), or `null` when the token is missing / expired / malformed / already
1422
- * consumed. A single `MULTI` `GET`+`DEL` guarantees at-most-once redemption.
1423
- */
1424
- declare const consumeOneTimeToken: (namespace: string, token: string) => Promise<string | null>;
1425
- /**
1426
- * Convenience wrapper over {@link consumeOneTimeToken} that JSON-parses the
1427
- * stored payload into `T`. Returns `null` on miss OR on a malformed JSON
1428
- * payload. Use when the token was issued with an object payload.
1429
- */
1430
- declare const consumeOneTimeTokenJson: <T>(namespace: string, token: string) => Promise<T | null>;
1431
- declare const oneTimeTokenKey: (namespace: string, token: string) => string;
1432
-
1433
- /**
1434
- * Core hook contract: framework-generic payload shapes + augmentable registry.
1435
- *
1436
- * Feature packages (login/sync/api/presence/...) extend `HookPayloads` via
1437
- * TypeScript module augmentation:
1438
- *
1439
- * ```ts
1440
- * // packages/<feature>/src/hookPayloads.ts
1441
- * declare module '@luckystack/core' {
1442
- * interface HookPayloads {
1443
- * myFeatureHook: { ... };
1444
- * }
1445
- * }
1446
- * export {};
1447
- * ```
1448
- *
1449
- * Module-augmentation files MUST be in a tsconfig `include` path for TS to
1450
- * pick up the merge; a side-effect import from the package's `index.ts`
1451
- * (`import './hookPayloads';`) guarantees this.
1452
- */
1453
-
1454
- /**
1455
- * Minimal session shape used inside core-owned hook payloads. Defined here
1456
- * (instead of imported from `@luckystack/login`) so `@luckystack/core` stays
1457
- * independent of login. Any concrete session type (`BaseSessionLayout`,
1458
- * project-level `SessionLayout` extending Prisma User, etc.) is structurally
1459
- * assignable to this shape.
1460
- */
1461
- interface HookSessionShape {
1462
- id: string;
1463
- token?: string;
1464
- email?: string | null;
1465
- name?: string | null;
1466
- avatar?: string | null;
1467
- avatarFallback?: string | null;
1468
- admin?: boolean | null;
1469
- language?: string | null;
1470
- roomCodes?: string[];
1471
- }
1472
- interface HookStopSignal {
1473
- stop: true;
1474
- errorCode: string;
1475
- httpStatus?: number;
1476
- /**
1477
- * Optional output override for per-recipient hooks (`preSyncRecipient`).
1478
- * When present on a stop signal the framework DOES NOT skip the recipient —
1479
- * instead it sends this value in place of the full `serverOutput`. Use to
1480
- * redact PII before delivery without excluding the socket from the fanout.
1481
- * Ignored by all other hook dispatch sites.
1482
- */
1483
- overrideOutput?: unknown;
1484
- }
1485
- /** Handlers return undefined / void to continue, or a stop signal to abort the main flow. */
1486
- type HookResult = void | undefined | HookStopSignal;
1487
- type HookHandler<TPayload> = (payload: TPayload) => Promise<HookResult> | HookResult;
1488
- interface PreApiValidatePayload {
1489
- routeName: string;
1490
- data: Record<string, unknown>;
1491
- user: HookSessionShape | null;
1492
- /**
1493
- * Optional transport tag — populated by the framework's socket/HTTP API
1494
- * handlers so hook subscribers can branch on it (e.g. open span with a
1495
- * different op name). Optional so consumer-side dispatchers don't have to
1496
- * set it explicitly.
1497
- */
1498
- transport?: 'socket' | 'http';
1499
- }
1500
- interface PostApiValidatePayload extends PreApiValidatePayload {
1501
- validation: {
1502
- status: 'success';
1503
- } | {
1504
- status: 'error';
1505
- message: string;
1506
- };
1507
- }
1508
- interface PreApiExecutePayload {
1509
- routeName: string;
1510
- data: Record<string, unknown>;
1511
- user: HookSessionShape | null;
1512
- /** Optional transport tag — see {@link PreApiValidatePayload.transport}. */
1513
- transport?: 'socket' | 'http';
1514
- }
1515
- interface PostApiExecutePayload {
1516
- routeName: string;
1517
- data: Record<string, unknown>;
1518
- user: HookSessionShape | null;
1519
- result: unknown;
1520
- error: Error | null;
1521
- durationMs: number;
1522
- /** Optional transport tag — see {@link PreApiValidatePayload.transport}. */
1523
- transport?: 'socket' | 'http';
1524
- }
1525
- interface ApiResponseEnvelope {
1526
- status: 'success' | 'error';
1527
- httpStatus?: number;
1528
- errorCode?: string;
1529
- message?: string;
1530
- [key: string]: unknown;
1531
- }
1532
- interface PreApiRespondPayload {
1533
- routeName: string;
1534
- user: HookSessionShape | null;
1535
- response: ApiResponseEnvelope;
1536
- /** Optional transport tag — see {@link PreApiValidatePayload.transport}. */
1537
- transport?: 'socket' | 'http';
1538
- }
1539
- interface PostApiRespondPayload {
1540
- routeName: string;
1541
- user: HookSessionShape | null;
1542
- response: ApiResponseEnvelope;
1543
- /** Optional transport tag — see {@link PreApiValidatePayload.transport}. */
1544
- transport?: 'socket' | 'http';
1545
- }
1546
- interface PreSyncAuthorizePayload {
1547
- routeName: string;
1548
- /** Raw (un-validated) data payload. May be reshape-able by the consumer. */
1549
- data: Record<string, unknown>;
1550
- user: HookSessionShape | null;
1551
- /** Room code / receiver token — the sync's intended audience. */
1552
- receiver: string;
1553
- /** Optional transport tag — see {@link PreApiValidatePayload.transport}. */
1554
- transport?: 'socket' | 'http';
1555
- }
1556
- interface PostSyncAuthorizePayload {
1557
- routeName: string;
1558
- data: Record<string, unknown>;
1559
- user: HookSessionShape | null;
1560
- receiver: string;
1561
- /** Optional transport tag — see {@link PreApiValidatePayload.transport}. */
1562
- transport?: 'socket' | 'http';
1563
- }
1564
- interface PreSyncValidatePayload {
1565
- routeName: string;
1566
- data: Record<string, unknown>;
1567
- user: HookSessionShape | null;
1568
- receiver: string;
1569
- /** Optional transport tag — see {@link PreApiValidatePayload.transport}. */
1570
- transport?: 'socket' | 'http';
1571
- }
1572
- interface PostSyncValidatePayload extends PreSyncValidatePayload {
1573
- validation: {
1574
- status: 'success';
1575
- } | {
1576
- status: 'error';
1577
- message: string;
1578
- };
1579
- }
1580
- interface PreSyncExecutePayload {
1581
- routeName: string;
1582
- data: Record<string, unknown>;
1583
- user: HookSessionShape | null;
1584
- receiver: string;
1585
- /** Optional transport tag — see {@link PreApiValidatePayload.transport}. */
1586
- transport?: 'socket' | 'http';
1587
- }
1588
- interface PostSyncExecutePayload {
1589
- routeName: string;
1590
- data: Record<string, unknown>;
1591
- user: HookSessionShape | null;
1592
- receiver: string;
1593
- result: unknown;
1594
- error: Error | null;
1595
- durationMs: number;
1596
- /** Optional transport tag — see {@link PreApiValidatePayload.transport}. */
1597
- transport?: 'socket' | 'http';
1598
- }
1599
- interface PreSyncFanoutPayload {
1600
- routeName: string;
1601
- data: Record<string, unknown>;
1602
- user: HookSessionShape | null;
1603
- receiver: string;
1604
- serverOutput: unknown;
1605
- /** Optional transport tag — see {@link PreApiValidatePayload.transport}. */
1606
- transport?: 'socket' | 'http';
1607
- }
1608
- interface PostSyncFanoutPayload extends PreSyncFanoutPayload {
1609
- recipientCount: number;
1610
- }
1611
- interface PreSyncStreamPayload {
1612
- routeName: string;
1613
- /** Stream chunk being sent (already serialised by the framework). */
1614
- chunk: unknown;
1615
- /** Final recipient — socket id when known, room code otherwise. */
1616
- recipient: string;
1617
- }
1618
- interface PostSyncStreamPayload extends PreSyncStreamPayload {
1619
- /** Total chunks streamed so far in this sync invocation. */
1620
- chunkIndex: number;
1621
- }
1622
- interface ApiErrorPayload {
1623
- route: string;
1624
- method?: string;
1625
- requestId?: string;
1626
- user?: HookSessionShape | null;
1627
- error: Error;
1628
- }
1629
- interface SyncErrorPayload {
1630
- route: string;
1631
- method?: string;
1632
- requestId?: string;
1633
- user?: HookSessionShape | null;
1634
- error: Error;
1635
- }
1636
- interface RateLimitExceededPayload {
1637
- /** Where the limit was hit — IP, user, or route. */
1638
- scope: 'ip' | 'user' | 'route' | 'auth';
1639
- /** The key that exceeded its limit (sanitized — no tokens). */
1640
- key: string;
1641
- /** Configured limit count for this key. */
1642
- limit: number;
1643
- /** Window in milliseconds. */
1644
- windowMs: number;
1645
- /** Current bucket count after the rejected request. */
1646
- count: number;
1647
- /** Optional route or transport context. */
1648
- route?: string;
1649
- ip?: string;
1650
- userId?: string;
1651
- }
1652
- interface CorsRejectedPayload {
1653
- /** Origin header sent by the client. */
1654
- origin: string;
1655
- /** Origin after framework normalization (scheme + host[:port]). */
1656
- normalizedOrigin: string;
1657
- /** Effective allowed-origins set at the time of rejection. */
1658
- allowedOrigins: string[];
1659
- /** Whether `allowLocalhost` was on. */
1660
- allowLocalhost: boolean;
1661
- /** Optional route the rejected request was for. */
1662
- route?: string;
1663
- /**
1664
- * Why the request was rejected (server HOK-27). Distinguishes a present-but-
1665
- * disallowed Origin (`'origin-not-allowed'`) from a MISSING Origin header
1666
- * (`'origin-missing'`, the 403 dispatched by the server's origin gate) and the
1667
- * normalization-failure case. Optional so existing `allowedOrigin()` callers
1668
- * that don't set it stay source-compatible.
1669
- */
1670
- reason?: 'origin-not-allowed' | 'origin-missing' | 'origin-malformed';
1671
- }
1672
- interface PreSessionRefreshPayload {
1673
- token: string;
1674
- userId: string | null;
1675
- oldTtl: number | null;
1676
- newTtl: number;
1677
- }
1678
- interface PostSessionRefreshPayload {
1679
- token: string;
1680
- userId: string | null;
1681
- oldTtl: number | null;
1682
- newTtl: number;
1683
- /** True if Redis EXPIRE succeeded; false on failure or non-existent key. */
1684
- applied: boolean;
1685
- }
1686
- interface SessionCreatedPayload {
1687
- token: string;
1688
- userId: string;
1689
- /** How the session came to exist (credentials login, OAuth, refresh-mint, ...). */
1690
- via?: string;
1691
- }
1692
- interface SessionRevokedPayload {
1693
- token: string;
1694
- userId: string | null;
1695
- /** Why the session was revoked (logout, kicked-by-new-device, admin, expiry, ...). */
1696
- reason?: string;
1697
- }
1698
- interface OnUploadStartPayload {
1699
- userId: string;
1700
- contentType: string;
1701
- sizeBytes: number;
1702
- uploadKind: 'avatar' | string;
1703
- }
1704
- interface OnUploadCompletePayload {
1705
- userId: string;
1706
- fileName: string;
1707
- sizeBytes: number;
1708
- uploadKind: 'avatar' | string;
1709
- }
1710
- interface PreAvatarServePayload {
1711
- /** The route path requested (`/avatars/:fileId`). */
1712
- routePath: string;
1713
- /** The allowlisted file id resolved from the route path. */
1714
- fileId: string;
1715
- }
1716
- interface PostAvatarServePayload {
1717
- routePath: string;
1718
- fileId: string;
1719
- /** Disk extension of the format that was served (e.g. `'webp'`). */
1720
- extension: string;
1721
- /** Content-Type header sent for the served file. */
1722
- contentType: string;
1723
- }
1724
- interface CsrfMismatchPayload {
1725
- /** Path of the rejected request. */
1726
- route: string;
1727
- /** HTTP method of the rejected request. */
1728
- method?: string;
1729
- /** Request id (X-Request-Id) for log correlation. */
1730
- requestId?: string;
1731
- /** User id from the session that the request claimed, if any. */
1732
- userId?: string;
1733
- /** Whether a token was provided in `x-csrf-token` (presence only — never the value). */
1734
- providedToken: boolean;
1735
- }
1736
- interface PreSocketMessagePayload {
1737
- /** Which socket pipeline received the message. */
1738
- channel: 'api' | 'sync';
1739
- /** Socket.io connection id of the originator. */
1740
- socketId: string;
1741
- /** Handshake address of the originator (best-effort; honours no proxy trust). */
1742
- ip: string;
1743
- /** Whether the originating socket carried a session token (presence only — never the value). */
1744
- authenticated: boolean;
1745
- /** Raw route name from the message envelope, when present (un-parsed, un-trusted). */
1746
- routeName?: string;
1747
- }
1748
- interface PreHttpRequestPayload {
1749
- /** HTTP method (always uppercase: GET/POST/...). */
1750
- method: string;
1751
- /** Request URL (path + query). */
1752
- url: string;
1753
- /** Caller-supplied or framework-generated `X-Request-Id`. */
1754
- requestId: string;
1755
- /** Origin header value (or empty string when absent). */
1756
- origin: string;
1757
- /**
1758
- * Subset of headers — `authorization`, `cookie`, etc. are NOT included
1759
- * to keep payloads safe for logging. Use the full request object via
1760
- * a custom route if you need raw access.
1761
- */
1762
- headers: Record<string, string>;
1763
- }
1764
- interface PostHttpRequestPayload {
1765
- /** HTTP method (always uppercase). */
1766
- method: string;
1767
- /** Request URL (path + query). */
1768
- url: string;
1769
- /** Caller-supplied or framework-generated `X-Request-Id`. */
1770
- requestId: string;
1771
- /** Final HTTP status code written to the response. */
1772
- statusCode: number;
1773
- /** Wall-clock duration from request receipt to response, in ms. */
1774
- durationMs: number;
1775
- }
1776
- interface ApiAuthRejectedPayload {
1777
- /** Resolved route name (`api/billing/getInvoice/v1`). */
1778
- routeName: string;
1779
- /** Why auth failed. `'login-required'` = no/invalid session on a login route;
1780
- * `'additional-failed'` = a session existed but an `additional[]` predicate
1781
- * rejected; `'invalid-condition'` = a misconfigured predicate (setup error). */
1782
- reason: 'login-required' | 'additional-failed' | 'invalid-condition';
1783
- /** Session user id when a (insufficient) session was present, else null. */
1784
- userId: string | null;
1785
- /** Resolved client IP (honours `http.trustProxy`). */
1786
- ip?: string;
1787
- /** Transport the request arrived on. */
1788
- transport?: 'socket' | 'http';
1789
- /** The specific `additional[]` key that failed, when `reason === 'additional-failed'`. */
1790
- failedKey?: string;
1791
- }
1792
- interface PreSyncRecipientPayload {
1793
- /** Resolved route name (`sync/board/moveCard/v1`). */
1794
- routeName: string;
1795
- /** Room code / receiver this fanout is for. */
1796
- receiver: string;
1797
- /** Socket id of THIS recipient. */
1798
- recipientSocketId: string;
1799
- /**
1800
- * Session token of THIS recipient, as read from its socket handshake — the
1801
- * same value the fan-out uses for `ignoreSelf`. `null` for an anonymous
1802
- * socket. Cheap: no session read is performed to produce it.
1803
- */
1804
- recipientToken: string | null;
1805
- /**
1806
- * Always `null`. Kept on the payload for backward compatibility; resolve the
1807
- * user from `recipientToken` when you need it.
1808
- */
1809
- recipientUserId: string | null;
1810
- /** The server-validated output about to be sent. */
1811
- serverOutput: unknown;
1812
- }
1813
- interface PreServerStopPayload {
1814
- /** Why the server is stopping — the signal name, or `'manual'` for a programmatic stop. */
1815
- reason: 'SIGTERM' | 'SIGINT' | 'SIGHUP' | 'manual';
1816
- /**
1817
- * Soft budget (ms) the server intends to wait for shutdown handlers + in-flight
1818
- * requests to drain before forcing exit. Handlers should not block longer than
1819
- * this. `undefined` when the server applies no deadline.
1820
- */
1821
- timeoutMs?: number;
1822
- }
1823
- interface HookPayloads {
1824
- preHttpRequest: PreHttpRequestPayload;
1825
- postHttpRequest: PostHttpRequestPayload;
1826
- preSocketMessage: PreSocketMessagePayload;
1827
- preApiValidate: PreApiValidatePayload;
1828
- postApiValidate: PostApiValidatePayload;
1829
- preApiExecute: PreApiExecutePayload;
1830
- postApiExecute: PostApiExecutePayload;
1831
- preApiRespond: PreApiRespondPayload;
1832
- transformApiResponse: PreApiRespondPayload;
1833
- postApiRespond: PostApiRespondPayload;
1834
- preSyncAuthorize: PreSyncAuthorizePayload;
1835
- postSyncAuthorize: PostSyncAuthorizePayload;
1836
- preSyncValidate: PreSyncValidatePayload;
1837
- postSyncValidate: PostSyncValidatePayload;
1838
- preSyncExecute: PreSyncExecutePayload;
1839
- postSyncExecute: PostSyncExecutePayload;
1840
- preSyncFanout: PreSyncFanoutPayload;
1841
- postSyncFanout: PostSyncFanoutPayload;
1842
- preSyncRecipient: PreSyncRecipientPayload;
1843
- preSyncStream: PreSyncStreamPayload;
1844
- postSyncStream: PostSyncStreamPayload;
1845
- apiError: ApiErrorPayload;
1846
- syncError: SyncErrorPayload;
1847
- apiAuthRejected: ApiAuthRejectedPayload;
1848
- rateLimitExceeded: RateLimitExceededPayload;
1849
- corsRejected: CorsRejectedPayload;
1850
- csrfMismatch: CsrfMismatchPayload;
1851
- preSessionRefresh: PreSessionRefreshPayload;
1852
- postSessionRefresh: PostSessionRefreshPayload;
1853
- sessionCreated: SessionCreatedPayload;
1854
- sessionRevoked: SessionRevokedPayload;
1855
- onUploadStart: OnUploadStartPayload;
1856
- onUploadComplete: OnUploadCompletePayload;
1857
- preAvatarServe: PreAvatarServePayload;
1858
- postAvatarServe: PostAvatarServePayload;
1859
- preServerStop: PreServerStopPayload;
1860
- }
1861
- interface PreErrorNormalizePayload {
1862
- response: ErrorResponseInput;
1863
- preferredLocale?: string | null;
1864
- userLanguage?: string | null;
1865
- fallbackHttpStatus?: number;
1866
- }
1867
- interface PostErrorNormalizePayload {
1868
- normalized: NormalizedErrorResponse;
1869
- preferredLocale?: string | null;
1870
- userLanguage?: string | null;
1871
- }
1872
- interface SyncHookPayloads {
1873
- preErrorNormalize: PreErrorNormalizePayload;
1874
- postErrorNormalize: PostErrorNormalizePayload;
1875
- }
1876
- type SyncHookName = keyof SyncHookPayloads;
1877
- type SyncHookHandler<TPayload> = (payload: TPayload) => void;
1878
- type HookName = keyof HookPayloads;
1879
-
1880
- type DispatchResult = {
1881
- stopped: false;
1882
- } | {
1883
- stopped: true;
1884
- signal: HookStopSignal;
1885
- };
1886
- declare const registerHook: <TName extends HookName>(name: TName, handler: HookHandler<HookPayloads[TName]>) => (() => void);
1887
- declare const dispatchHook: <TName extends HookName>(name: TName, payload: HookPayloads[TName]) => Promise<DispatchResult>;
1888
- declare const clearAllHooks: () => void;
1889
- declare const registerSyncHook: <TName extends SyncHookName>(name: TName, handler: SyncHookHandler<SyncHookPayloads[TName]>) => (() => void);
1890
- declare const dispatchSyncHook: <TName extends SyncHookName>(name: TName, payload: SyncHookPayloads[TName]) => void;
1891
-
1892
- export { type ApiAuthRejectedPayload, type ApiErrorPayload, type ApiResponseEnvelope, AuthProps, type AvatarConfig, type AvatarConfigInput, BOOT_KEY_PREFIX, BaseSessionLayout, type BootUuidHeartbeat, type CheckRateLimitParams, type CookiePrefixConstraints, type CorsRejectedPayload, type CreateLocalizedNormalizerInput, type CsrfConfig, type CsrfCookieOptions, type CsrfMismatchPayload, DEFAULT_AVATAR_CONFIG, DEFAULT_CLIENT_KEY, DEFAULT_CSRF_CONFIG, DEFAULT_ENV_FILES, DEFAULT_REDACTED_LOG_KEYS, DEPTH_TRUNCATED_PLACEHOLDER, type DbHealthCheck, type DbHealthResult, DeepPartial, type DispatchResult, type EmailAttachment, type EmailDeliveryOutcome, type EmailMessage, type EmailResult, type EmailSendContext, type EmailSender, type EmailSenderRegistry, type ErrorFormatter, type ErrorFormatterContext, ErrorParam, ErrorResponseInput, type ErrorTracker, type ErrorTrackerContext, type ErrorTrackerEvent, type ErrorTrackerUser, type FormatterEnvelope, GENERATED_API_DOCS_PATH, GENERATED_API_SCHEMAS_PATH, GENERATED_SOCKET_TYPES_PATH, type GetIoInstanceOptions, type GetRoomSocketsOptions, HealthHashConfig, type HealthHashDescriptor, type HookHandler, type HookName, type HookPayloads, type HookResult, type HookSessionShape, type HookStopSignal, type HttpMethod, type LanguageCode, LocalSocketEnumerationError, type LocaleReloader, type LocalizedNormalizer, type NormalizeErrorResponseInput, NormalizedErrorResponse, type OnUploadCompletePayload, type OnUploadStartPayload, type OneTimeTokenHandle, PUBLIC_DIR, type PeerRequire, type PostApiExecutePayload, type PostApiRespondPayload, type PostApiValidatePayload, type PostAvatarServePayload, type PostErrorNormalizePayload, type PostHttpRequestPayload, type PostSessionRefreshPayload, type PostSyncAuthorizePayload, type PostSyncExecutePayload, type PostSyncFanoutPayload, type PostSyncValidatePayload, type PreApiExecutePayload, type PreApiRespondPayload, type PreApiValidatePayload, type PreAvatarServePayload, type PreCaptureFilter, type PreErrorNormalizePayload, type PreHttpRequestPayload, type PreServerStopPayload, type PreSessionRefreshPayload, type PreSocketMessagePayload, type PreSyncAuthorizePayload, type PreSyncExecutePayload, type PreSyncFanoutPayload, type PreSyncRecipientPayload, type PreSyncValidatePayload, type ProcessUploadInput, type ProcessUploadResult, REDACTED_PLACEHOLDER, ROOT_DIR, type RateLimitExceededPayload, type RateLimitResult, type RateLimitStrategy, type RedisConnectionOptions, type RedisKeyFormatter, type Registry, type RegistryOptions, type ResolveClientIpParams, type RoomNameFormatter, type RoomNameFormatterContext, type RoomSocket, type RuntimeApiMapsResult, type RuntimeEnv, type RuntimeMapsProvider, type RuntimeSyncMapsResult, SERVER_DIR, SERVER_FUNCTIONS_DIR, SHARED_DIR, SRC_DIR, type SessionCreatedPayload, type SessionLogoutInput, type SessionProvider, type SessionRevokedPayload, type SessionSaveResult, type SocketMiddleware, type SpanHandle, type SpanResult, type SyncErrorPayload, type SyncHookHandler, type SyncHookName, type SyncHookPayloads, TEST_DIRECTORY_NAMES, TEST_FILE_PATTERN, TEST_RESET_ONLY_PARAM, TEST_RESET_TARGETS, TSCONFIG_ALIAS_FILES, type TestResetTarget, type TranslateFunction, type TranslateInput, UNKNOWN_CLIENT_IP, UPLOADS_DIR, type ValidationResult$1 as ValidationResult, abortAllForSocket, abortApiByResponseIndex, abortSyncByCb, acquireLease, allowedOrigin, type apiMessage, appendErrorTracker, applyCookiePrefixConstraints, applyErrorFormatter, applySocketMiddlewares, applyStrayKeyPrefix, attachSocketRedisAdapter, bootstrapEnv, captureException, captureExceptionAcrossTrackers, captureMessage, captureMessageAcrossTrackers, checkRateLimit, clearAllHooks, clearAllRateLimits, clearRateLimit, clearSocketMiddlewares, collectSynchronizedEnvKeys, computeSynchronizedEnvHashes, consumeOneTimeToken, consumeOneTimeTokenJson, createLocalizedNormalizer, createRegistry, defaultRateLimitStrategy, defaultRedisKeyFormatter, defaultRoomNameFormatter, deriveTokenBucketId, describeHealthHashConfig, dispatchHook, dispatchSyncHook, ensurePeerDepInstalled, env, escapeHtml, extractLanguageFromHeader, extractTokenFromRequest, extractTokenFromSocket, flushErrorTrackers, formatKey, formatRoomName, getActiveErrorTrackers, getAvatarConfig, getBindAddress, getCookieValue, getCsrfConfig, getCurrentErrorTrackerIdentity, getDbHealthCheck, getEffectiveHttpMethod, getEmailSender, getEmailSenderByName, getEnv, getEnvFiles, getErrorFormatter, getGeneratedApiDocsPath, getGeneratedApiSchemasPath, getGeneratedSocketTypesPath, getIoInstance, getLocaleReloader, getLocalizedNormalizer, getParams, getPrismaClient, getPrismaClientFor, getPrismaClientKeys, getPublicDir, getRateLimitStatus, getRateLimitStrategy, getRedactedLogKeys, getRedisClient, getRedisClientFor, getRedisClientKeys, getRedisConnectionOptions, getRedisKeyFormatter, getRegisteredSessionProvider, getRoomNameFormatter, getRoomSockets, getRuntimeApiMaps, getRuntimeSyncMaps, getServerDir, getServerFunctionDirs, getServerFunctionsDir, getSharedDir, getSocketMiddlewares, getSrcDir, getUploadsDir, hasCookie, hashSynchronizedValue, hashSynchronizedValueWith, inferHttpMethod, initConsolelog, initSharedSentry, isDbHealthCheckRegistered, isEmailSenderRegistered, isFalsy, isGetMethodName, isLocalizedNormalizerRegistered, isLoopbackIp, isMethodAllowed, isPrismaClientRegistered, isPrismaClientResolvable, isProduction, isProductionRuntime, isRedactedLogKey, isRedisClientRegistered, isRuntimeMapsProviderRegistered, isSessionProviderRegistered, isTestDirectory, isTestFile, isTestResetTarget, isTestRuntime, issueOneTimeToken, listEmailSenderNames, loadEnvFiles, loadPeer, normalizeErrorResponse, normalizeOrigin, oneTimeTokenKey, performLogout, prisma, processUpload, readBootUuid, readSession, rebuildDefaultRedisClient, recordMetricAcrossTrackers, redis, refreshBootUuid, registerApiAbortController, registerAvatarConfig, registerBindAddress, registerBoundAddress, registerCsrfConfig, registerDbHealthCheck, registerEmailSender, registerEmailSenders, registerErrorFormatter, registerErrorTracker, registerErrorTrackers, registerHook, registerLocaleReloader, registerLocalizedNormalizer, registerPreCaptureFilter, registerPrismaClient, registerRateLimitStrategy, registerRedactedLogKeys, registerRedisClient, registerRedisKeyFormatter, registerRoomNameFormatter, registerRuntimeMapsProvider, registerSessionProvider, registerSocketMiddleware, registerStrayPrefixCommand, registerSyncAbortController, registerSyncHook, releaseLease, removeSession, renewLease, resetClientsForTests, resetCsrfConfigForTests, resetDbHealthCheckForTests, resetDefaultRedisClient, resetRedactedLogKeysForTests, resetRedisKeyFormatterForTests, resetSessionProviderForTests, resolveClientIp, resolveDevCallbackUrl, resolveEnvKey, resolveErrorMessage, resolveFromRoot, resolveHealthHashConfig, resolveHealthHashConfigFromDescriptor, resolveRuntimeMode, resolveSocketAdapterKey, runWithErrorTrackerIdentity, runWithErrorTrackerIdentityScope, sanitizeErrorString, sanitizeErrorStrings, sanitizeForLog, serveAvatar, setCurrentErrorTrackerIdentity, setErrorTrackerUser, setIoInstance, setSentryUser, startBootUuidHeartbeat, startSpan, startSpanAcrossTrackers, startSpanHandle, type syncMessage, tryCatch, unregisterApiAbortController, unregisterSyncAbortController, validateInputByType, validateRequest, writeBootUuid, writeSession };
1
+ export { default as sleep } from './sleep.js';
2
+ export { default as tryCatch } from './tryCatch.js';
3
+ export { default as tryCatchSync } from './tryCatchSync.js';
4
+ export { deepMerge, isPlainObject } from './configUtils.js';
5
+ export type { DeepPartial } from './configUtils.js';
6
+ export { createRegistry } from './createRegistry.js';
7
+ export type { Registry, RegistryOptions } from './createRegistry.js';
8
+ export { escapeHtml } from './escapeHtml.js';
9
+ export { ROUTED_DATA_QUERY_KEY, buildRoutedGetUrl } from './routedDataQuery.js';
10
+ export { ensurePeerDepInstalled, loadPeer } from './peerDeps.js';
11
+ export type { PeerRequire } from './peerDeps.js';
12
+ export * from './serviceRoute.js';
13
+ export * from './socketEvents.js';
14
+ export { isTestFile, isTestDirectory, TEST_FILE_PATTERN, TEST_DIRECTORY_NAMES, } from './testFileConvention.js';
15
+ export { registerRoomNameFormatter, getRoomNameFormatter, formatRoomName, defaultRoomNameFormatter, } from './roomNameFormatterRegistry.js';
16
+ export type { RoomNameFormatter, RoomNameFormatterContext } from './roomNameFormatterRegistry.js';
17
+ export * from './responseNormalizer.js';
18
+ export * from './localizedNormalizer.js';
19
+ export { registerErrorFormatter, getErrorFormatter, applyErrorFormatter, } from './errorFormatterRegistry.js';
20
+ export type { ErrorFormatter, ErrorFormatterContext, FormatterEnvelope } from './errorFormatterRegistry.js';
21
+ export { validatePagePath, DEFAULT_PAGE_ROUTE_RULES, } from './pageRouteValidation.js';
22
+ export type { PageRouteRules, PagePathValidationResult, } from './pageRouteValidation.js';
23
+ export type { ApiTypeMap, SyncTypeMap, StreamPayload } from './apiTypeStubs.js';
24
+ export type { apiMessage, syncMessage } from './socketTypes.js';
25
+ export { setIoInstance, getIoInstance } from './socketTypes.js';
26
+ export type { GetIoInstanceOptions } from './socketTypes.js';
27
+ export { LocalSocketEnumerationError } from './localSocketEnumerationGuard.js';
28
+ export { getRoomSockets } from './roomSockets.js';
29
+ export type { GetRoomSocketsOptions, RoomSocket } from './roomSockets.js';
30
+ export { registerProjectConfig, getProjectConfig, getProjectName, isProjectConfigRegistered, DEFAULT_PROJECT_CONFIG, } from './projectConfig.js';
31
+ export { registerPortOverride, getPortOverride, resetPortOverrideForTests, } from './portOverrideRegistry.js';
32
+ export type { ProjectConfig, ProjectConfigInput, AppConfig, LoggingConfig, RateLimitingConfig, SessionConfig, HttpConfig, HttpStreamConfig, HealthHashConfig, SecurityHeadersConfig, CorsConfig, AuthConfig, AuthRateLimitConfig, RateLimitIdentity, RateLimitIdentityParams, PasswordPolicyConfig, SocketConfig, ApiConfig, ValidationConfig, SyncConfig, SyncStreamThrottleConfig, SyncFlushPressureConfig, OfflineQueueConfig, DevConfig, PathsConfig, } from './projectConfig.js';
33
+ export { registerRuntimeMapsProvider, getRuntimeApiMaps, getRuntimeSyncMaps, isRuntimeMapsProviderRegistered, } from './runtimeMapsRegistry.js';
34
+ export { registerApiMethodMap, getRegisteredApiMethod, isApiMethodMapRegistered, } from './apiMethodMapRegistry.js';
35
+ export type { HttpMethodLiteral, ApiMethodMap } from './apiMethodMapRegistry.js';
36
+ export type { RuntimeMapsProvider, RuntimeApiMapsResult, RuntimeSyncMapsResult, } from './runtimeMapsRegistry.js';
37
+ export { registerNotifier, getNotifier, notify, } from './notifier.js';
38
+ export type { Notifier, NotifyInput, NotifyParam } from './notifier.js';
39
+ export { registerEmailSender, registerEmailSenders, getEmailSender, getEmailSenderByName, listEmailSenderNames, isEmailSenderRegistered, } from './emailRegistry.js';
40
+ export type { EmailDeliveryOutcome, EmailSendContext, EmailSender, EmailMessage, EmailAttachment, EmailResult, EmailSenderRegistry, } from './emailRegistry.js';
41
+ export { registerDeployConfig, getDeployConfig, isDeployConfigRegistered, } from './deployConfigRegistry.js';
42
+ export type { DeployConfigShape, DeployResourceShape, DeployEnvironmentShape, DeployRoutingShape, DeployDevelopmentShape, } from './deployConfigRegistry.js';
43
+ export { registerServicesConfig, getServicesConfig, isServicesConfigRegistered, } from './servicesConfigRegistry.js';
44
+ export type { ServicesConfigShape, ServiceDefinition, PresetDefinition, } from './servicesConfigRegistry.js';
45
+ export { registerLocaleReloader, getLocaleReloader, } from './localeReloader.js';
46
+ export type { LocaleReloader } from './localeReloader.js';
47
+ export type { statusContent, SOCKETSTATUS } from './socketStatusTypes.js';
48
+ export type { BaseSessionLayout, SessionLocation, AuthProps, Jsonify } from './sessionTypes.js';
49
+ export * from './sentrySetup.js';
50
+ export { registerErrorTracker, registerErrorTrackers, appendErrorTracker, getActiveErrorTrackers, captureExceptionAcrossTrackers, captureMessageAcrossTrackers, setErrorTrackerUser, recordMetricAcrossTrackers, startSpanAcrossTrackers, startSpanHandle, registerPreCaptureFilter, flushErrorTrackers, sanitizeErrorString, sanitizeErrorStrings, } from './errorTrackerRegistry.js';
51
+ export type { ErrorTracker, ErrorTrackerContext, ErrorTrackerUser, ErrorTrackerEvent, SpanResult, SpanHandle, PreCaptureFilter, } from './errorTrackerRegistry.js';
52
+ export { runWithErrorTrackerIdentity, runWithErrorTrackerIdentityScope, setCurrentErrorTrackerIdentity, getCurrentErrorTrackerIdentity, } from './errorTrackerIdentity.js';
53
+ export * from './env.js';
54
+ export * from './db.js';
55
+ export { registerDbHealthCheck, getDbHealthCheck, isDbHealthCheckRegistered, resetDbHealthCheckForTests, } from './dbHealthCheck.js';
56
+ export type { DbHealthCheck, DbHealthResult } from './dbHealthCheck.js';
57
+ export { redis, getRedisConnectionOptions, registerStrayPrefixCommand, resetDefaultRedisClient, rebuildDefaultRedisClient } from './redis.js';
58
+ export type { RedisConnectionOptions } from './redis.js';
59
+ export { registerSecretsResolvedListener, notifySecretsResolved } from './secretsResolved.js';
60
+ export type { SecretsResolvedListener } from './secretsResolved.js';
61
+ export { registerRedisKeyFormatter, getRedisKeyFormatter, resetRedisKeyFormatterForTests, defaultRedisKeyFormatter, formatKey, applyStrayKeyPrefix, } from './redisKeyFormatter.js';
62
+ export type { RedisKeyFormatter } from './redisKeyFormatter.js';
63
+ export { acquireLease, renewLease, releaseLease } from './lease.js';
64
+ export { DEFAULT_CLIENT_KEY, registerPrismaClient, registerRedisClient, getPrismaClient, getRedisClient, getPrismaClientFor, getRedisClientFor, getPrismaClientKeys, getRedisClientKeys, isPrismaClientRegistered, isRedisClientRegistered, resetClientsForTests, } from './clients.js';
65
+ export { attachSocketRedisAdapter, resolveSocketAdapterKey } from './socketRedisAdapter.js';
66
+ export { writeBootUuid, readBootUuid, refreshBootUuid, startBootUuidHeartbeat, resolveEnvKey, BOOT_KEY_PREFIX, type BootUuidHeartbeat, } from './bootUuid.js';
67
+ export { collectSynchronizedEnvKeys, computeSynchronizedEnvHashes, hashSynchronizedValue, hashSynchronizedValueWith, resolveHealthHashConfig, describeHealthHashConfig, resolveHealthHashConfigFromDescriptor, } from './synchronizedEnvHashes.js';
68
+ export type { HealthHashDescriptor } from './synchronizedEnvHashes.js';
69
+ export { initConsolelog } from './consoleLog.js';
70
+ export { registerLogger, getLogger, isLoggerRegistered, resetLoggerForTests, createDevLogger, } from './loggerRegistry.js';
71
+ export type { Logger, LoggerContext } from './loggerRegistry.js';
72
+ export { registerRedactedLogKeys, getRedactedLogKeys, isRedactedLogKey, resetRedactedLogKeysForTests, sanitizeForLog, DEFAULT_REDACTED_LOG_KEYS, REDACTED_PLACEHOLDER, DEPTH_TRUNCATED_PLACEHOLDER, } from './redactedLogKeys.js';
73
+ export * from './cookies.js';
74
+ export * from './httpApiUtils.js';
75
+ export * from './paths.js';
76
+ export { registerSocketMiddleware, getSocketMiddlewares, clearSocketMiddlewares, applySocketMiddlewares, } from './socketMiddlewareRegistry.js';
77
+ export type { SocketMiddleware } from './socketMiddlewareRegistry.js';
78
+ export { registerCsrfConfig, getCsrfConfig, resetCsrfConfigForTests, DEFAULT_CSRF_CONFIG, } from './csrfConfig.js';
79
+ export type { CsrfConfig, CsrfCookieOptions } from './csrfConfig.js';
80
+ export { serveAvatar } from './serveAvatars.js';
81
+ export { processUpload } from './processUpload.js';
82
+ export type { ProcessUploadInput, ProcessUploadResult } from './processUpload.js';
83
+ export { registerBindAddress, registerBoundAddress, getBindAddress, resolveDevCallbackUrl } from './bindAddress.js';
84
+ export { registerAvatarConfig, getAvatarConfig, DEFAULT_AVATAR_CONFIG, } from './avatarConfig.js';
85
+ export type { AvatarConfig, AvatarConfigInput } from './avatarConfig.js';
86
+ export { default as getParams } from './getParams.js';
87
+ export { extractTokenFromSocket } from './extractToken.js';
88
+ export { extractTokenFromRequest } from './extractTokenFromRequest.js';
89
+ export { resolveClientIp, isLoopbackIp, UNKNOWN_CLIENT_IP, deriveTokenBucketId } from './resolveClientIp.js';
90
+ export type { ResolveClientIpParams } from './resolveClientIp.js';
91
+ export { registerSessionProvider, getRegisteredSessionProvider, isSessionProviderRegistered, resetSessionProviderForTests, readSession, writeSession, removeSession, performLogout, } from './sessionProviderRegistry.js';
92
+ export type { SessionProvider, SessionSaveResult, SessionLogoutInput } from './sessionProviderRegistry.js';
93
+ export { validateRequest, isFalsy } from './validateRequest.js';
94
+ export type { ValidationResult } from './validateRequest.js';
95
+ export { checkRateLimit, getRateLimitStatus, clearRateLimit, clearAllRateLimits, registerRateLimitStrategy, getRateLimitStrategy, defaultRateLimitStrategy, } from './rateLimiter.js';
96
+ export type { RateLimitStrategy, CheckRateLimitParams, RateLimitResult } from './rateLimiter.js';
97
+ export { validateInputByType } from './runtimeTypeValidation.js';
98
+ export { default as allowedOrigin, normalizeOrigin } from './checkOrigin.js';
99
+ export { isOnline, enqueueApiRequest, enqueueSyncRequest, removeApiQueueItem, removeSyncQueueItem, removeApiQueueItemsByKey, flushApiQueue, flushSyncQueue, getApiQueueSize, getSyncQueueSize, } from './offlineQueue.js';
100
+ export type { QueueDropReason } from './offlineQueue.js';
101
+ export { socket, setSocket, incrementResponseIndex, waitForSocket } from './socketState.js';
102
+ export { registerSyncAbortController, unregisterSyncAbortController, abortSyncByCb, registerApiAbortController, unregisterApiAbortController, abortApiByResponseIndex, abortAllForSocket, } from './cancelRegistry.js';
103
+ export { getCsrfToken, clearCsrfToken, httpFetch } from './csrf.js';
104
+ export { TEST_RESET_TARGETS, TEST_RESET_ONLY_PARAM, isTestResetTarget, } from './testResetTargets.js';
105
+ export type { TestResetTarget } from './testResetTargets.js';
106
+ export { issueOneTimeToken, consumeOneTimeToken, consumeOneTimeTokenJson, oneTimeTokenKey, } from './oneTimeToken.js';
107
+ export type { OneTimeTokenHandle } from './oneTimeToken.js';
108
+ export { registerHook, dispatchHook, clearAllHooks, registerSyncHook, dispatchSyncHook } from './hooks/registry.js';
109
+ export type { DispatchResult } from './hooks/registry.js';
110
+ export type { HookSessionShape, HookStopSignal, HookResult, HookHandler, HookName, HookPayloads, PreApiValidatePayload, PostApiValidatePayload, PreApiExecutePayload, PostApiExecutePayload, PreApiRespondPayload, PostApiRespondPayload, ApiResponseEnvelope, PreHttpRequestPayload, PostHttpRequestPayload, PreSocketMessagePayload, PreSyncAuthorizePayload, PostSyncAuthorizePayload, PreSyncValidatePayload, PostSyncValidatePayload, PreSyncExecutePayload, PostSyncExecutePayload, PreSyncFanoutPayload, PostSyncFanoutPayload, PreSyncRecipientPayload, ApiErrorPayload, SyncErrorPayload, ApiAuthRejectedPayload, RateLimitExceededPayload, CorsRejectedPayload, CsrfMismatchPayload, PreSessionRefreshPayload, PostSessionRefreshPayload, SessionCreatedPayload, SessionRevokedPayload, OnUploadStartPayload, OnUploadCompletePayload, PreAvatarServePayload, PostAvatarServePayload, PreServerStopPayload, SyncHookName, SyncHookHandler, SyncHookPayloads, PreErrorNormalizePayload, PostErrorNormalizePayload, } from './hooks/types.js';