@luckystack/core 0.1.9 → 0.2.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.
package/dist/index.js CHANGED
@@ -1,350 +1,156 @@
1
- // src/sleep.ts
2
- function sleep(ms) {
3
- return new Promise((resolve) => setTimeout(resolve, ms));
4
- }
1
+ import {
2
+ DEFAULT_CSRF_CONFIG,
3
+ DEFAULT_PAGE_ROUTE_RULES,
4
+ INVALID_ERROR_RESPONSE_CODE,
5
+ buildApiResponseEventName,
6
+ buildApiStreamEventName,
7
+ buildGetJoinedRoomsResponseEventName,
8
+ buildJoinRoomResponseEventName,
9
+ buildLeaveRoomResponseEventName,
10
+ buildSyncProgressEventName,
11
+ buildSyncResponseEventName,
12
+ clearCsrfToken,
13
+ defaultHttpStatusForResponse,
14
+ enqueueApiRequest,
15
+ enqueueSyncRequest,
16
+ flushApiQueue,
17
+ flushSyncQueue,
18
+ getApiQueueSize,
19
+ getCsrfConfig,
20
+ getCsrfToken,
21
+ getEffectiveHttpMethod,
22
+ getNotifier,
23
+ getRegisteredApiMethod,
24
+ getSyncQueueSize,
25
+ httpFetch,
26
+ incrementResponseIndex,
27
+ inferHttpMethod,
28
+ isApiMethodMapRegistered,
29
+ isErrorParamArray,
30
+ isGetMethodName,
31
+ isMethodAllowed,
32
+ isOnline,
33
+ normalizeErrorResponseCore,
34
+ notify,
35
+ parseServiceRouteName,
36
+ parseTransportRouteName,
37
+ registerApiMethodMap,
38
+ registerCsrfConfig,
39
+ registerNotifier,
40
+ removeApiQueueItem,
41
+ removeApiQueueItemsByKey,
42
+ removeSyncQueueItem,
43
+ resetCsrfConfigForTests,
44
+ setSocket,
45
+ sleep,
46
+ socket,
47
+ socketEventNames,
48
+ tryCatch,
49
+ tryCatchSync,
50
+ validatePagePath,
51
+ waitForSocket
52
+ } from "./chunk-OZTB2RL2.js";
53
+ import {
54
+ DEFAULT_PROJECT_CONFIG,
55
+ deepMerge,
56
+ getProjectConfig,
57
+ getProjectName,
58
+ isPlainObject,
59
+ isProjectConfigRegistered,
60
+ registerProjectConfig
61
+ } from "./chunk-W62GGT2P.js";
62
+ import {
63
+ DEFAULT_REDACTED_LOG_KEYS,
64
+ DEPTH_TRUNCATED_PLACEHOLDER,
65
+ REDACTED_PLACEHOLDER,
66
+ appendErrorTracker,
67
+ captureException,
68
+ captureExceptionAcrossTrackers,
69
+ captureMessage,
70
+ captureMessageAcrossTrackers,
71
+ createDevLogger,
72
+ flushErrorTrackers,
73
+ getActiveErrorTrackers,
74
+ getCurrentErrorTrackerIdentity,
75
+ getLogger,
76
+ getRedactedLogKeys,
77
+ initSharedSentry,
78
+ isLoggerRegistered,
79
+ isRedactedLogKey,
80
+ recordMetricAcrossTrackers,
81
+ registerErrorTracker,
82
+ registerErrorTrackers,
83
+ registerLogger,
84
+ registerPreCaptureFilter,
85
+ registerRedactedLogKeys,
86
+ resetLoggerForTests,
87
+ resetRedactedLogKeysForTests,
88
+ runWithErrorTrackerIdentity,
89
+ runWithErrorTrackerIdentityScope,
90
+ sanitizeErrorString,
91
+ sanitizeErrorStrings,
92
+ sanitizeForLog,
93
+ setCurrentErrorTrackerIdentity,
94
+ setErrorTrackerUser,
95
+ setSentryUser,
96
+ startSpan,
97
+ startSpanAcrossTrackers,
98
+ startSpanHandle
99
+ } from "./chunk-V2WKGTZH.js";
100
+ import {
101
+ createRegistry
102
+ } from "./chunk-USQA6QTX.js";
5
103
 
6
- // src/errorTrackerRegistry.ts
7
- var activeTrackers = [];
8
- var registerErrorTracker = (tracker) => {
9
- activeTrackers = [tracker];
10
- };
11
- var registerErrorTrackers = (trackers) => {
12
- activeTrackers = [...trackers];
13
- };
14
- var getActiveErrorTrackers = () => activeTrackers;
15
- var captureExceptionAcrossTrackers = (error, context) => {
16
- for (const tracker of activeTrackers) {
17
- try {
18
- tracker.captureException(error, context);
19
- } catch {
20
- }
21
- }
22
- };
23
- var captureMessageAcrossTrackers = (message, level, context) => {
24
- for (const tracker of activeTrackers) {
25
- try {
26
- tracker.captureMessage(message, level, context);
27
- } catch {
28
- }
29
- }
30
- };
31
- var setErrorTrackerUser = (user) => {
32
- for (const tracker of activeTrackers) {
33
- try {
34
- tracker.setUser(user);
35
- } catch {
36
- }
37
- }
38
- };
39
- var recordMetricAcrossTrackers = (name, value, tags) => {
40
- for (const tracker of activeTrackers) {
41
- if (tracker.recordMetric) {
42
- try {
43
- tracker.recordMetric(name, value, tags);
44
- } catch {
45
- }
46
- }
47
- }
48
- };
49
- var startSpanAcrossTrackers = (name, op, fn) => {
50
- const first = activeTrackers.find((t) => t.startSpan);
51
- if (!first?.startSpan) return fn();
52
- return first.startSpan(name, op, fn);
53
- };
104
+ // src/escapeHtml.ts
105
+ var HTML_ESCAPE_MAP = {
106
+ "&": "&",
107
+ "<": "&lt;",
108
+ ">": "&gt;",
109
+ '"': "&quot;",
110
+ "'": "&#39;"
111
+ };
112
+ var escapeHtml = (str) => str.replaceAll(/[&<>"']/g, (c) => HTML_ESCAPE_MAP[c] ?? c);
54
113
 
55
- // src/sentrySetup.ts
56
- var sentry;
57
- var initSharedSentry = (instance) => {
58
- sentry = instance;
59
- };
60
- var captureException = (error, context) => {
61
- if (sentry) {
62
- if (context) {
63
- sentry.captureException(error, { extra: context });
64
- } else {
65
- sentry.captureException(error);
66
- }
67
- }
68
- captureExceptionAcrossTrackers(error, context);
69
- };
70
- var captureMessage = (message, level = "info", context) => {
71
- if (sentry) {
72
- if (context) {
73
- sentry.captureMessage(message, { level, extra: context });
74
- } else {
75
- sentry.captureMessage(message, level);
76
- }
77
- }
78
- captureMessageAcrossTrackers(message, level, context);
79
- };
80
- var setSentryUser = (user) => {
81
- sentry?.setUser(user);
82
- setErrorTrackerUser(user);
83
- };
84
- var startSpan = (name, op) => {
85
- if (sentry?.startInactiveSpan) {
86
- return sentry.startInactiveSpan({ name, op });
87
- }
88
- startSpanAcrossTrackers(name, op, () => {
89
- });
90
- };
91
-
92
- // src/tryCatch.ts
93
- async function tryCatch(func, params, context) {
114
+ // src/peerDeps.ts
115
+ import { createRequire } from "module";
116
+ var defaultRequire = createRequire(import.meta.url);
117
+ var ensurePeerDepInstalled = (packageName, hint, requireFn = defaultRequire) => {
94
118
  try {
95
- const response = await func(params);
96
- return [null, response];
97
- } catch (error) {
98
- captureException(error, context);
99
- return [error, null];
100
- }
101
- }
102
-
103
- // src/serviceRoute.ts
104
- var VERSION_SEGMENT_REGEX = /^v\d+$/;
105
- var normalizeRawRoute = (value) => {
106
- return value.trim().replaceAll("\\", "/").replaceAll(/^\/+/g, "").replaceAll(/\/+$/g, "");
107
- };
108
- var hasEmptySegments = (value) => {
109
- return value.split("/").some((segment) => segment.length === 0);
110
- };
111
- var parseServiceRouteName = (value) => {
112
- const normalized = normalizeRawRoute(value);
113
- if (!normalized) {
114
- return { status: "error", reason: "Route name cannot be empty." };
115
- }
116
- if (hasEmptySegments(normalized)) {
117
- return { status: "error", reason: "Route name cannot contain empty segments." };
118
- }
119
- const segments = normalized.split("/");
120
- if (segments.length < 2) {
121
- return { status: "error", reason: "Route name must include service and route segments." };
122
- }
123
- const [service, ...routeSegments] = segments;
124
- const routeName = routeSegments.join("/");
125
- if (!service || !routeName) {
126
- return { status: "error", reason: "Route name must include service and route segments." };
127
- }
128
- return {
129
- status: "success",
130
- normalizedRouteName: `${service}/${routeName}`,
131
- service,
132
- routeName
133
- };
134
- };
135
- var parseTransportRouteName = ({
136
- value,
137
- prefix
138
- }) => {
139
- const normalized = normalizeRawRoute(value);
140
- if (!normalized) {
141
- return { status: "error", reason: "Transport route cannot be empty." };
142
- }
143
- if (hasEmptySegments(normalized)) {
144
- return { status: "error", reason: "Transport route cannot contain empty segments." };
145
- }
146
- const segments = normalized.split("/");
147
- const routeSegments = segments[0] === prefix ? segments.slice(1) : segments;
148
- if (routeSegments.length < 3) {
149
- return {
150
- status: "error",
151
- reason: `Transport route must use ${prefix}/{service}/{name}/{version} format.`
152
- };
153
- }
154
- const version = routeSegments.at(-1);
155
- if (!version || !VERSION_SEGMENT_REGEX.test(version)) {
156
- return { status: "error", reason: "Route version segment must match v{number}." };
157
- }
158
- const serviceRouteToken = routeSegments.slice(0, -1).join("/");
159
- const serviceRoute = parseServiceRouteName(serviceRouteToken);
160
- if (serviceRoute.status === "error") {
161
- return {
162
- status: "error",
163
- reason: serviceRoute.reason
164
- };
119
+ requireFn.resolve(packageName);
120
+ } catch {
121
+ throw new Error(
122
+ `The \`${packageName}\` package is not installed but a feature that needs it was used. ${hint}`
123
+ );
165
124
  }
166
- return {
167
- status: "success",
168
- normalizedFullName: `${prefix}/${serviceRoute.normalizedRouteName}/${version}`,
169
- version,
170
- serviceRoute
171
- };
172
125
  };
173
-
174
- // src/socketEvents.ts
175
- var withIndex = (prefix, responseIndex2) => {
176
- return `${prefix}${String(responseIndex2)}`;
177
- };
178
- var socketEventNames = {
179
- apiRequest: "apiRequest",
180
- apiCancel: "apiCancel",
181
- sync: "sync",
182
- syncCancel: "syncCancel",
183
- joinRoom: "joinRoom",
184
- leaveRoom: "leaveRoom",
185
- getJoinedRooms: "getJoinedRooms",
186
- updateLocation: "updateLocation",
187
- updateSession: "updateSession",
188
- sessionReplaced: "sessionReplaced",
189
- logout: "logout",
190
- intentionalDisconnect: "intentionalDisconnect",
191
- intentionalReconnect: "intentionalReconnect",
192
- userAfk: "userAfk",
193
- userBack: "userBack",
194
- connect: "connect",
195
- disconnect: "disconnect",
196
- reconnectAttempt: "reconnect_attempt",
197
- connectError: "connect_error",
198
- apiResponsePrefix: "apiResponse-",
199
- apiStreamPrefix: "apiStream-",
200
- syncResponsePrefix: "sync-",
201
- syncProgressPrefix: "sync-progress-",
202
- joinRoomResponsePrefix: "joinRoom-",
203
- leaveRoomResponsePrefix: "leaveRoom-",
204
- getJoinedRoomsResponsePrefix: "getJoinedRooms-"
205
- };
206
- var buildApiResponseEventName = (responseIndex2) => {
207
- return withIndex(socketEventNames.apiResponsePrefix, responseIndex2);
208
- };
209
- var buildApiStreamEventName = (responseIndex2) => {
210
- return withIndex(socketEventNames.apiStreamPrefix, responseIndex2);
211
- };
212
- var buildSyncResponseEventName = (responseIndex2) => {
213
- return withIndex(socketEventNames.syncResponsePrefix, responseIndex2);
214
- };
215
- var buildSyncProgressEventName = (responseIndex2) => {
216
- return withIndex(socketEventNames.syncProgressPrefix, responseIndex2);
217
- };
218
- var buildJoinRoomResponseEventName = (responseIndex2) => {
219
- return withIndex(socketEventNames.joinRoomResponsePrefix, responseIndex2);
220
- };
221
- var buildLeaveRoomResponseEventName = (responseIndex2) => {
222
- return withIndex(socketEventNames.leaveRoomResponsePrefix, responseIndex2);
223
- };
224
- var buildGetJoinedRoomsResponseEventName = (responseIndex2) => {
225
- return withIndex(socketEventNames.getJoinedRoomsResponsePrefix, responseIndex2);
126
+ var loadPeer = (packageName, hint, requireFn = defaultRequire) => {
127
+ ensurePeerDepInstalled(packageName, hint, requireFn);
128
+ return requireFn(packageName);
226
129
  };
227
130
 
228
- // src/responseNormalizer.ts
229
- var INVALID_ERROR_RESPONSE_CODE = "error.invalidResponse";
230
- var isErrorParamArray = (value) => {
231
- return Array.isArray(value) && value.every((item) => {
232
- return !!item && typeof item === "object" && typeof item.key === "string" && ["string", "number", "boolean"].includes(typeof item.value);
233
- });
234
- };
235
- var normalizeErrorResponseCore = ({
236
- response,
237
- fallbackHttpStatus,
238
- fallbackErrorCode,
239
- resolveMessage
240
- }) => {
241
- const normalizedParams = isErrorParamArray(response.errorParams) ? response.errorParams : void 0;
242
- const errorCodeValue = typeof response.errorCode === "string" ? response.errorCode.trim() : "";
243
- const hasErrorCode = errorCodeValue.length > 0;
244
- const finalErrorCode = hasErrorCode ? errorCodeValue : fallbackErrorCode ?? INVALID_ERROR_RESPONSE_CODE;
245
- const providedHttpStatus = typeof response.httpStatus === "number" ? response.httpStatus : void 0;
246
- const message = resolveMessage ? resolveMessage({ errorCode: finalErrorCode, errorParams: normalizedParams }) : finalErrorCode;
247
- return {
248
- status: "error",
249
- message,
250
- errorCode: finalErrorCode,
251
- errorParams: normalizedParams,
252
- httpStatus: providedHttpStatus ?? fallbackHttpStatus
253
- };
254
- };
255
- var defaultHttpStatusForResponse = ({
256
- status,
257
- explicitHttpStatus,
258
- fallbackErrorStatus = 400
259
- }) => {
260
- if (typeof explicitHttpStatus === "number") return explicitHttpStatus;
261
- return status === "success" ? 200 : fallbackErrorStatus;
262
- };
263
-
264
- // src/loggerRegistry.ts
265
- var defaultLogger = {
266
- debug: (message, context) => {
267
- if (context === void 0) {
268
- console.debug(message);
269
- } else {
270
- console.debug(message, context);
271
- }
272
- },
273
- info: (message, context) => {
274
- if (context === void 0) {
275
- console.info(message);
276
- } else {
277
- console.info(message, context);
278
- }
279
- },
280
- warn: (message, context) => {
281
- if (context === void 0) {
282
- console.warn(message);
283
- } else {
284
- console.warn(message, context);
285
- }
286
- },
287
- error: (message, error, context) => {
288
- if (error !== void 0 && context !== void 0) console.error(message, error, context);
289
- else if (error !== void 0) console.error(message, error);
290
- else if (context === void 0) {
291
- console.error(message);
292
- } else {
293
- console.error(message, context);
294
- }
295
- }
296
- };
297
- var createDevLogger = () => ({
298
- debug: (message, context) => {
299
- if (context === void 0) {
300
- console.log(message, "cyan");
301
- } else {
302
- console.log(message, context, "cyan");
303
- }
304
- },
305
- info: (message, context) => {
306
- if (context === void 0) {
307
- console.log(message);
308
- } else {
309
- console.log(message, context);
310
- }
311
- },
312
- warn: (message, context) => {
313
- if (context === void 0) {
314
- console.log(message, "yellow");
315
- } else {
316
- console.log(message, context, "yellow");
317
- }
318
- },
319
- error: (message, error, context) => {
320
- if (error !== void 0 && context !== void 0) console.log(message, error, context, "red");
321
- else if (error !== void 0) console.log(message, error, "red");
322
- else if (context === void 0) {
323
- console.log(message, "red");
324
- } else {
325
- console.log(message, context, "red");
326
- }
327
- }
328
- });
329
- var activeLogger = defaultLogger;
330
- var isRegistered = false;
331
- var registerLogger = (logger) => {
332
- activeLogger = logger;
333
- isRegistered = true;
334
- };
335
- var getLogger = () => activeLogger;
336
- var isLoggerRegistered = () => isRegistered;
337
- var resetLoggerForTests = () => {
338
- activeLogger = defaultLogger;
339
- isRegistered = false;
340
- };
131
+ // src/roomNameFormatterRegistry.ts
132
+ var identityFormatter = (rawRoomName) => rawRoomName;
133
+ var registry = createRegistry(identityFormatter);
134
+ var registerRoomNameFormatter = (formatter2) => {
135
+ registry.register(formatter2);
136
+ };
137
+ var getRoomNameFormatter = () => registry.get();
138
+ var formatRoomName = (rawRoomName, ctx) => registry.get()(rawRoomName, ctx);
139
+ var defaultRoomNameFormatter = identityFormatter;
341
140
 
342
141
  // src/hooks/registry.ts
343
142
  var hookHandlers = /* @__PURE__ */ new Map();
344
143
  var registerHook = (name, handler) => {
345
144
  const existing = hookHandlers.get(name) ?? [];
346
- existing.push(handler);
145
+ const wrapped = handler;
146
+ existing.push(wrapped);
347
147
  hookHandlers.set(name, existing);
148
+ return () => {
149
+ const handlers = hookHandlers.get(name);
150
+ if (!handlers) return;
151
+ const index = handlers.indexOf(wrapped);
152
+ if (index !== -1) handlers.splice(index, 1);
153
+ };
348
154
  };
349
155
  var dispatchHook = async (name, payload) => {
350
156
  const handlers = hookHandlers.get(name) ?? [];
@@ -353,8 +159,14 @@ var dispatchHook = async (name, payload) => {
353
159
  try {
354
160
  result = await handler(payload);
355
161
  } catch (error) {
356
- getLogger().error(`hook: handler for "${name}" threw`, error, { hook: name });
357
- captureException(error, { hook: name });
162
+ try {
163
+ getLogger().error(`hook: handler for "${name}" threw`, error, { hook: name });
164
+ } catch {
165
+ }
166
+ try {
167
+ captureException(error, { hook: name });
168
+ } catch {
169
+ }
358
170
  continue;
359
171
  }
360
172
  if (result !== void 0) {
@@ -370,8 +182,15 @@ var clearAllHooks = () => {
370
182
  var syncHookHandlers = /* @__PURE__ */ new Map();
371
183
  var registerSyncHook = (name, handler) => {
372
184
  const existing = syncHookHandlers.get(name) ?? [];
373
- existing.push(handler);
185
+ const wrapped = handler;
186
+ existing.push(wrapped);
374
187
  syncHookHandlers.set(name, existing);
188
+ return () => {
189
+ const handlers = syncHookHandlers.get(name);
190
+ if (!handlers) return;
191
+ const index = handlers.indexOf(wrapped);
192
+ if (index !== -1) handlers.splice(index, 1);
193
+ };
375
194
  };
376
195
  var dispatchSyncHook = (name, payload) => {
377
196
  const handlers = syncHookHandlers.get(name) ?? [];
@@ -379,8 +198,14 @@ var dispatchSyncHook = (name, payload) => {
379
198
  try {
380
199
  handler(payload);
381
200
  } catch (error) {
382
- getLogger().error(`hook: sync handler for "${name}" threw`, error, { hook: name });
383
- captureException(error, { hook: name });
201
+ try {
202
+ getLogger().error(`hook: sync handler for "${name}" threw`, error, { hook: name });
203
+ } catch {
204
+ }
205
+ try {
206
+ captureException(error, { hook: name });
207
+ } catch {
208
+ }
384
209
  }
385
210
  }
386
211
  };
@@ -467,81 +292,33 @@ var resolveErrorMessage = (input) => activeNormalizer.resolveErrorMessage(input)
467
292
  var extractLanguageFromHeader = (header) => activeNormalizer.extractLanguageFromHeader(header);
468
293
 
469
294
  // src/errorFormatterRegistry.ts
470
- var activeFormatter = null;
295
+ var registry2 = createRegistry(null);
471
296
  var registerErrorFormatter = (formatter2) => {
472
- activeFormatter = formatter2;
297
+ registry2.register(formatter2);
473
298
  };
474
- var getErrorFormatter = () => activeFormatter;
299
+ var getErrorFormatter = () => registry2.get();
475
300
  var applyErrorFormatter = (input) => {
476
301
  const { response, routeName, transport, userId, perRouteFormatter } = input;
477
302
  if (response.status !== "error") return response;
478
303
  const ctx = { routeName, transport, userId };
479
304
  const errorEnvelope = response;
480
305
  if (perRouteFormatter) {
481
- try {
482
- return perRouteFormatter(errorEnvelope, ctx);
483
- } catch (error) {
306
+ const [error, formatted] = tryCatchSync(() => perRouteFormatter(errorEnvelope, ctx));
307
+ if (!error && formatted) return formatted;
308
+ if (error) {
484
309
  console.error(`[errorFormatter] per-route formatter for ${routeName} threw:`, error);
485
310
  }
486
311
  }
487
- const globalFormatter = activeFormatter;
312
+ const globalFormatter = registry2.get();
488
313
  if (globalFormatter) {
489
- try {
490
- return globalFormatter(errorEnvelope, ctx);
491
- } catch (error) {
492
- console.error(`[errorFormatter] global formatter threw on ${routeName}:`, error);
493
- return response;
494
- }
314
+ const [error, formatted] = tryCatchSync(() => globalFormatter(errorEnvelope, ctx));
315
+ if (!error && formatted) return formatted;
316
+ console.error(`[errorFormatter] global formatter threw on ${routeName}:`, error);
317
+ return response;
495
318
  }
496
319
  return response;
497
320
  };
498
321
 
499
- // src/pageRouteValidation.ts
500
- var DEFAULT_PAGE_ROUTE_RULES = {
501
- privateFolderPrefix: "_",
502
- scaffoldIgnoredFolders: [
503
- "_api",
504
- "_sync",
505
- "_function",
506
- "_functions",
507
- "_component",
508
- "_components",
509
- "_provider",
510
- "_providers",
511
- "_locale",
512
- "_locales",
513
- "_socket",
514
- "_sockets",
515
- "_shared",
516
- "_server"
517
- ]
518
- };
519
- var validatePagePath = (srcRelativePath, rules = DEFAULT_PAGE_ROUTE_RULES) => {
520
- const normalized = srcRelativePath.replaceAll("\\", "/").replace(/^\.\//, "").replace(/^\/+/, "");
521
- const segments = normalized.split("/").filter((segment) => segment.length > 0);
522
- if (segments.length === 0) {
523
- return { valid: false, reason: "empty path" };
524
- }
525
- const filename = segments.at(-1);
526
- if (filename !== "page.tsx" && filename !== "page.jsx") {
527
- return { valid: false, reason: "not a page file (expected page.tsx)" };
528
- }
529
- const folderSegments = segments.slice(0, -1);
530
- const prefix = rules.privateFolderPrefix;
531
- const reserved = rules.scaffoldIgnoredFolders;
532
- for (const reservedName of reserved) {
533
- if (folderSegments.includes(reservedName)) {
534
- return { valid: false, reason: `page.tsx cannot live inside reserved folder ${reservedName}` };
535
- }
536
- }
537
- const visibleSegments = folderSegments.filter((segment) => !segment.startsWith(prefix));
538
- if (folderSegments.length > 0 && visibleSegments.length === 0) {
539
- return { valid: false, reason: "no URL segment left after stripping underscore folders" };
540
- }
541
- const route = visibleSegments.length === 0 ? "/" : `/${visibleSegments.join("/")}`;
542
- return { valid: true, route };
543
- };
544
-
545
322
  // src/socketTypes.ts
546
323
  var _ioInstance = null;
547
324
  var setIoInstance = (io) => {
@@ -549,288 +326,80 @@ var setIoInstance = (io) => {
549
326
  };
550
327
  var getIoInstance = () => _ioInstance;
551
328
 
552
- // src/projectConfig.ts
553
- var DEFAULT_PROJECT_CONFIG = {
554
- app: {
555
- publicUrl: ""
556
- },
557
- logging: {
558
- devLogs: false,
559
- devNotifications: false,
560
- socketStatus: false,
561
- socketStartup: false,
562
- stream: false
563
- },
564
- rateLimiting: {
565
- enabled: true,
566
- store: "memory",
567
- redisKeyPrefix: "rate-limit",
568
- defaultApiLimit: 60,
569
- defaultIpLimit: 100,
570
- windowMs: 6e4,
571
- cleanupIntervalMs: 6e4
572
- },
573
- session: {
574
- basedToken: false,
575
- expiryDays: 7,
576
- allowMultiple: false,
577
- perBrowser: "single",
578
- perUser: "single",
579
- maxConcurrentPerUser: null,
580
- onConflict: "revokeOld",
581
- notifyOldDeviceOnRevoke: true,
582
- //? Empty default — `getProjectName()` reads `process.env.PROJECT_NAME` at
583
- //? call time and falls back to `'luckystack'`. Avoids capturing env at
584
- //? module-load before dotenv runs.
585
- projectName: ""
586
- },
587
- http: {
588
- sessionCookieName: "token",
589
- sessionCookieSameSite: "Strict",
590
- sessionCookiePath: "/",
591
- requestBodyMaxBytes: 1024 * 1024,
592
- healthEndpoint: "/_health",
593
- liveEndpoint: "/livez",
594
- readyEndpoint: "/readyz",
595
- testResetEndpoint: "/_test/reset",
596
- stream: {
597
- queryParam: "stream",
598
- enabledValue: "true",
599
- connectedComment: ": connected"
600
- },
601
- securityHeaders: {
602
- frameOptions: "SAMEORIGIN",
603
- referrerPolicy: "no-referrer",
604
- xssProtection: "1; mode=block",
605
- contentTypeOptions: "nosniff"
606
- },
607
- cors: {
608
- allowedMethods: "GET, POST, PUT, DELETE, OPTIONS",
609
- allowedHeaders: "Content-Type, Authorization, X-Session-Based-Token, X-CSRF-Token, X-Request-Id",
610
- exposedHeaders: "X-Session-Token, X-Request-Id",
611
- credentials: true,
612
- allowedOrigins: [],
613
- allowLocalhost: false
614
- }
615
- },
616
- auth: {
617
- oauthStateTtlSeconds: 60 * 10,
618
- passwordMinLength: 8,
619
- passwordMaxLength: 191,
620
- passwordPolicy: {
621
- minLength: 8,
622
- maxLength: 191,
623
- requireUppercase: false,
624
- requireLowercase: false,
625
- requireNumber: false,
626
- requireSpecial: false,
627
- forbidCommon: true
628
- },
629
- emailMaxLength: 191,
630
- nameMaxLength: 191,
631
- bcryptRounds: 10,
632
- providerAccountStrategy: "per-provider",
633
- forgotPassword: "disabled",
634
- passwordResetTtlSeconds: 60 * 60,
635
- emailChangeTtlSeconds: 60 * 60
636
- },
637
- socket: {
638
- maxHttpBufferSize: 5 * 1024 * 1024,
639
- pingTimeout: 2e4,
640
- pingInterval: 25e3
641
- },
642
- sync: {
643
- streamThrottle: {
644
- flushAtChars: 32,
645
- flushEveryMs: 50,
646
- field: "chunk"
647
- },
648
- fanoutYieldEvery: 100,
649
- fanoutYieldMs: 1
650
- },
651
- offlineQueue: {
652
- maxSize: 200,
653
- maxAgeMs: 60 * 60 * 1e3,
654
- dropPolicy: "drop-oldest"
655
- },
656
- dev: {
657
- hotReloadDebounceMs: 120,
658
- watcherStabilityThresholdMs: 120,
659
- watcherPollIntervalMs: 20
660
- },
661
- paths: {
662
- srcDir: "src",
663
- serverDir: "server",
664
- sharedDir: "shared",
665
- uploadsDir: "uploads",
666
- publicDir: "public",
667
- serverFunctionsDir: "server/functions",
668
- serverFunctionDirs: ["functions", "shared"],
669
- generatedSocketTypes: "src/_sockets/apiTypes.generated.ts",
670
- generatedApiSchemas: "src/_sockets/apiInputSchemas.generated.ts",
671
- generatedApiDocs: "src/docs/apiDocs.generated.json"
672
- },
673
- defaultLanguage: "en",
674
- defaultTheme: "light",
675
- socketActivityBroadcaster: false,
676
- socketStatusIndicator: false,
677
- locationProviderEnabled: false,
678
- loginRedirectUrl: "/"
679
- };
680
- var isPlainObject = (value) => {
681
- if (value === null || typeof value !== "object") return false;
682
- const proto = Object.getPrototypeOf(value);
683
- return proto === Object.prototype || proto === null;
684
- };
685
- var deepMerge = (base, override) => {
686
- if (override === void 0) return base;
687
- if (!isPlainObject(base) || !isPlainObject(override)) {
688
- return override ?? base;
689
- }
690
- const result = { ...base };
691
- for (const [key, overrideValue] of Object.entries(override)) {
692
- if (overrideValue === void 0) continue;
693
- const baseValue = base[key];
694
- result[key] = isPlainObject(baseValue) && isPlainObject(overrideValue) ? deepMerge(baseValue, overrideValue) : overrideValue;
695
- }
696
- return result;
697
- };
698
- var activeConfig = DEFAULT_PROJECT_CONFIG;
699
- var isRegistered2 = false;
700
- var registerProjectConfig = (config) => {
701
- activeConfig = deepMerge(DEFAULT_PROJECT_CONFIG, config);
702
- isRegistered2 = true;
703
- };
704
- var getProjectConfig = () => activeConfig;
705
- var getProjectName = () => {
706
- const fromConfig = activeConfig.session.projectName;
707
- if (fromConfig && fromConfig.length > 0) return fromConfig;
708
- const fromEnv = process.env.PROJECT_NAME;
709
- if (fromEnv && fromEnv.length > 0) return fromEnv;
710
- return "luckystack";
711
- };
712
- var isProjectConfigRegistered = () => isRegistered2;
713
-
714
329
  // src/runtimeMapsRegistry.ts
715
330
  var emptyApi = { apisObject: {}, functionsObject: {} };
716
331
  var emptySync = { syncObject: {}, functionsObject: {} };
717
- var activeProvider = {
332
+ var registry3 = createRegistry({
718
333
  getRuntimeApiMaps: () => Promise.resolve(emptyApi),
719
334
  getRuntimeSyncMaps: () => Promise.resolve(emptySync)
335
+ });
336
+ var registerRuntimeMapsProvider = (provider2) => {
337
+ registry3.register(provider2);
720
338
  };
721
- var providerRegistered = false;
722
- var registerRuntimeMapsProvider = (provider) => {
723
- activeProvider = provider;
724
- providerRegistered = true;
725
- };
726
- var isRuntimeMapsProviderRegistered = () => providerRegistered;
727
- var getRuntimeApiMaps = async () => activeProvider.getRuntimeApiMaps();
728
- var getRuntimeSyncMaps = async () => activeProvider.getRuntimeSyncMaps();
729
-
730
- // src/apiMethodMapRegistry.ts
731
- var activeMap = null;
732
- var registerApiMethodMap = (map) => {
733
- activeMap = map;
734
- };
735
- var getRegisteredApiMethod = (pagePath, apiName, version) => {
736
- if (!activeMap) return void 0;
737
- return activeMap[pagePath]?.[apiName]?.[version];
738
- };
739
- var isApiMethodMapRegistered = () => activeMap !== null;
740
-
741
- // src/notifier.ts
742
- var noopNotifier = {
743
- success: () => {
744
- },
745
- error: () => {
746
- },
747
- info: () => {
748
- },
749
- warning: () => {
750
- }
751
- };
752
- var activeNotifier = noopNotifier;
753
- var registerNotifier = (notifier) => {
754
- activeNotifier = notifier;
755
- };
756
- var getNotifier = () => activeNotifier;
757
- var notify = {
758
- success: (input) => {
759
- activeNotifier.success(input);
760
- },
761
- error: (input) => {
762
- activeNotifier.error(input);
763
- },
764
- info: (input) => {
765
- activeNotifier.info(input);
766
- },
767
- warning: (input) => {
768
- activeNotifier.warning(input);
769
- }
770
- };
339
+ var isRuntimeMapsProviderRegistered = () => registry3.isRegistered();
340
+ var getRuntimeApiMaps = async () => registry3.get().getRuntimeApiMaps();
341
+ var getRuntimeSyncMaps = async () => registry3.get().getRuntimeSyncMaps();
771
342
 
772
343
  // src/emailRegistry.ts
773
344
  var activeSender = null;
774
- var registry = {};
345
+ var registry4 = {};
775
346
  var registerEmailSender = (sender) => {
776
347
  activeSender = sender;
777
- registry = { ...registry, default: sender };
348
+ registry4 = { ...registry4, default: sender };
778
349
  };
779
350
  var registerEmailSenders = (senders) => {
780
- registry = { ...senders };
351
+ registry4 = { ...senders };
781
352
  if (senders.default) {
782
353
  activeSender = senders.default;
783
354
  }
784
355
  };
785
- var getEmailSender = () => activeSender ?? registry.default ?? null;
356
+ var getEmailSender = () => activeSender ?? registry4.default ?? null;
786
357
  var getEmailSenderByName = (name) => {
787
- if (registry[name]) return registry[name];
358
+ if (registry4[name]) return registry4[name];
788
359
  if (name === "default" && activeSender) return activeSender;
789
360
  return null;
790
361
  };
791
- var listEmailSenderNames = () => Object.keys(registry);
792
- var isEmailSenderRegistered = () => activeSender !== null || Object.keys(registry).length > 0;
362
+ var listEmailSenderNames = () => Object.keys(registry4);
363
+ var isEmailSenderRegistered = () => activeSender !== null || Object.keys(registry4).length > 0;
793
364
 
794
365
  // src/deployConfigRegistry.ts
795
366
  var DEFAULT_DEPLOY_CONFIG = {
796
367
  resources: {}
797
368
  };
798
- var activeConfig2 = DEFAULT_DEPLOY_CONFIG;
799
- var registered = false;
800
- var registerDeployConfig = (config) => {
801
- activeConfig2 = config;
802
- registered = true;
803
- return activeConfig2;
804
- };
805
- var getDeployConfig = () => activeConfig2;
806
- var isDeployConfigRegistered = () => registered;
369
+ var registry5 = createRegistry(DEFAULT_DEPLOY_CONFIG);
370
+ var registerDeployConfig = (config) => registry5.register(config);
371
+ var getDeployConfig = () => registry5.get();
372
+ var isDeployConfigRegistered = () => registry5.isRegistered();
807
373
 
808
374
  // src/servicesConfigRegistry.ts
809
- var activeServicesConfig = null;
810
- var registerServicesConfig = (config) => {
811
- activeServicesConfig = config;
812
- return activeServicesConfig;
813
- };
814
- var getServicesConfig = () => {
815
- if (!activeServicesConfig) {
816
- throw new Error(
817
- "@luckystack/core: services config has not been registered. Call `registerServicesConfig({...})` from your `services.config.ts` (which should be imported as a side-effect during boot)."
818
- );
375
+ var registry6 = createRegistry(
376
+ //? Unregistered sentinel — `get` never returns this because `resolveDefault`
377
+ //? throws first. Typed as the real shape so the slot stays well-typed.
378
+ { services: {}, presets: {} },
379
+ {
380
+ resolveDefault: () => {
381
+ throw new Error(
382
+ "@luckystack/core: services config has not been registered. Call `registerServicesConfig({...})` from your `services.config.ts` (which should be imported as a side-effect during boot)."
383
+ );
384
+ }
819
385
  }
820
- return activeServicesConfig;
821
- };
822
- var isServicesConfigRegistered = () => activeServicesConfig !== null;
386
+ );
387
+ var registerServicesConfig = (config) => registry6.register(config);
388
+ var getServicesConfig = () => registry6.get();
389
+ var isServicesConfigRegistered = () => registry6.isRegistered();
823
390
 
824
391
  // src/localeReloader.ts
825
- var activeReloader = null;
392
+ var registry7 = createRegistry(null);
826
393
  var registerLocaleReloader = (reloader) => {
827
- activeReloader = reloader;
394
+ registry7.register(reloader);
828
395
  return reloader;
829
396
  };
830
- var getLocaleReloader = () => activeReloader;
397
+ var getLocaleReloader = () => registry7.get();
831
398
 
832
399
  // src/env.ts
833
- import { config as loadDotenv } from "dotenv";
400
+ import { existsSync, readFileSync } from "fs";
401
+ import path from "path";
402
+ import { config as loadDotenv, parse as parseDotenv } from "dotenv";
834
403
  import { z } from "zod";
835
404
  var EnvSchema = z.object({
836
405
  NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
@@ -851,10 +420,33 @@ var getEnvFiles = () => {
851
420
  }
852
421
  return DEFAULT_ENV_FILES;
853
422
  };
423
+ var reportDuplicateEnvKeys = (files) => {
424
+ const seenIn = /* @__PURE__ */ new Map();
425
+ for (const file of files) {
426
+ const filePath = path.resolve(process.cwd(), file);
427
+ if (!existsSync(filePath)) continue;
428
+ const parsed = parseDotenv(readFileSync(filePath, "utf8"));
429
+ for (const key of Object.keys(parsed)) {
430
+ const list = seenIn.get(key) ?? [];
431
+ list.push(file);
432
+ seenIn.set(key, list);
433
+ }
434
+ }
435
+ for (const [key, sources] of seenIn) {
436
+ if (sources.length > 1) {
437
+ const winner = sources.at(-1);
438
+ console.warn(`[env] "${key}" is defined in ${sources.join(" + ")}; "${winner}" wins. Removing it from only one file lets the other file's value resurface.`);
439
+ }
440
+ }
441
+ };
854
442
  var loadEnvFiles = () => {
855
- for (const [index, file] of getEnvFiles().entries()) {
443
+ const files = getEnvFiles();
444
+ for (const [index, file] of files.entries()) {
856
445
  loadDotenv({ path: file, override: index > 0 });
857
446
  }
447
+ if (process.env.LUCKYSTACK_ENV_DEBUG) {
448
+ reportDuplicateEnvKeys(files);
449
+ }
858
450
  };
859
451
  var applyResolvedDefaultsToProcessEnv = (resolvedEnv) => {
860
452
  process.env.NODE_ENV = process.env.NODE_ENV ?? resolvedEnv.NODE_ENV;
@@ -881,6 +473,7 @@ var bootstrapEnv = () => {
881
473
  };
882
474
  var env = bootstrapEnv();
883
475
  var isProduction = env.NODE_ENV === "production";
476
+ var getEnv = () => bootstrapEnv();
884
477
 
885
478
  // src/db.ts
886
479
  import { PrismaClient } from "@prisma/client";
@@ -938,9 +531,6 @@ var buildDefaultPrismaClient = () => {
938
531
  return globalThis.__luckystackPrisma;
939
532
  };
940
533
  setDefaultPrismaResolver(buildDefaultPrismaClient);
941
- if (process.env.NODE_ENV !== "production") {
942
- buildDefaultPrismaClient();
943
- }
944
534
  var prisma = new Proxy({}, {
945
535
  //? Bind methods to the real client so a CAPTURED method keeps its `this`.
946
536
  //? Without binding, a detached call (`const q = prisma.$queryRaw; q(...)`)
@@ -951,9 +541,9 @@ var prisma = new Proxy({}, {
951
541
  //? Mirrors the `redis` proxy. Non-function reads (model delegates like
952
542
  //? `prisma.user`) pass through untouched, so their methods bind to the
953
543
  //? delegate as before.
954
- get: (_target, prop, receiver) => {
544
+ get: (_target, prop, _receiver) => {
955
545
  const real = getPrismaClient();
956
- const value = Reflect.get(real, prop, receiver);
546
+ const value = Reflect.get(real, prop, real);
957
547
  if (typeof value !== "function") return value;
958
548
  const fn = value;
959
549
  return fn.bind(real);
@@ -984,22 +574,31 @@ var applyStrayKeyPrefix = (key) => key.includes(":") ? key : `${getProjectName()
984
574
 
985
575
  // src/redis.ts
986
576
  var cachedDefault = null;
987
- var MAX_REDIS_RECONNECT_ATTEMPTS = 50;
577
+ var DEFAULT_MAX_REDIS_RECONNECT_ATTEMPTS = 50;
578
+ var DEFAULT_MAX_REDIS_BACKOFF_MS = 2e3;
579
+ var readPositiveIntEnv = (name, fallback) => {
580
+ const raw = process.env[name];
581
+ if (!raw) return fallback;
582
+ const parsed = Number.parseInt(raw, 10);
583
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
584
+ };
988
585
  var buildDefaultRedisClient = () => {
989
586
  if (cachedDefault) return cachedDefault;
587
+ const maxReconnectAttempts = readPositiveIntEnv("LUCKYSTACK_REDIS_MAX_RECONNECTS", DEFAULT_MAX_REDIS_RECONNECT_ATTEMPTS);
588
+ const maxBackoffMs = readPositiveIntEnv("LUCKYSTACK_REDIS_MAX_BACKOFF_MS", DEFAULT_MAX_REDIS_BACKOFF_MS);
990
589
  cachedDefault = new Redis({
991
590
  host: env.REDIS_HOST,
992
591
  port: Number.parseInt(env.REDIS_PORT, 10),
993
592
  ...process.env.REDIS_USER && { username: process.env.REDIS_USER },
994
593
  ...process.env.REDIS_PASSWORD && { password: process.env.REDIS_PASSWORD },
995
594
  retryStrategy(times) {
996
- if (times > MAX_REDIS_RECONNECT_ATTEMPTS) {
595
+ if (times > maxReconnectAttempts) {
997
596
  getLogger().error(
998
- `Redis unreachable after ${String(MAX_REDIS_RECONNECT_ATTEMPTS)} reconnect attempts; giving up. Check REDIS_HOST / REDIS_PORT / credentials and that Redis is reachable.`
597
+ `Redis unreachable after ${String(maxReconnectAttempts)} reconnect attempts; giving up. Check REDIS_HOST / REDIS_PORT / credentials and that Redis is reachable. Raise LUCKYSTACK_REDIS_MAX_RECONNECTS for longer outage tolerance.`
999
598
  );
1000
599
  return null;
1001
600
  }
1002
- return Math.min(times * 50, 2e3);
601
+ return Math.min(times * 50, maxBackoffMs);
1003
602
  }
1004
603
  });
1005
604
  cachedDefault.on("ready", async () => {
@@ -1074,13 +673,32 @@ var STRAY_PREFIX_COMMANDS = /* @__PURE__ */ new Set([
1074
673
  "setbit",
1075
674
  "bitcount"
1076
675
  ]);
676
+ var ALL_ARGS_ARE_KEYS_COMMANDS = /* @__PURE__ */ new Set([
677
+ "del",
678
+ "unlink",
679
+ "exists",
680
+ "touch",
681
+ "mget"
682
+ ]);
683
+ var registerStrayPrefixCommand = (...commands) => {
684
+ for (const command of commands) {
685
+ STRAY_PREFIX_COMMANDS.add(command.toLowerCase());
686
+ }
687
+ };
1077
688
  var redisProxy = new Proxy({}, {
1078
- get: (_target, prop, receiver) => {
689
+ get: (_target, prop, _receiver) => {
1079
690
  const real = getRedisClient();
1080
- const value = Reflect.get(real, prop, receiver);
691
+ const value = Reflect.get(real, prop, real);
1081
692
  if (typeof value !== "function") return value;
1082
693
  const fn = value;
1083
- if (typeof prop === "string" && STRAY_PREFIX_COMMANDS.has(prop.toLowerCase())) {
694
+ const command = typeof prop === "string" ? prop.toLowerCase() : "";
695
+ if (command && ALL_ARGS_ARE_KEYS_COMMANDS.has(command)) {
696
+ return (...args) => {
697
+ const prefixed = args.map((arg) => typeof arg === "string" ? applyStrayKeyPrefix(arg) : arg);
698
+ return fn.apply(real, prefixed);
699
+ };
700
+ }
701
+ if (command && STRAY_PREFIX_COMMANDS.has(command)) {
1084
702
  return (...args) => {
1085
703
  if (args.length > 0 && typeof args[0] === "string") {
1086
704
  args[0] = applyStrayKeyPrefix(args[0]);
@@ -1093,6 +711,7 @@ var redisProxy = new Proxy({}, {
1093
711
  has: (_target, prop) => Reflect.has(getRedisClient(), prop)
1094
712
  });
1095
713
  var redis = redisProxy;
714
+ var redis_default = redisProxy;
1096
715
 
1097
716
  // src/lease.ts
1098
717
  import { randomBytes } from "crypto";
@@ -1136,16 +755,16 @@ var releaseLease = async (name, token) => {
1136
755
 
1137
756
  // src/socketRedisAdapter.ts
1138
757
  import { createAdapter } from "@socket.io/redis-adapter";
1139
- var attachSocketRedisAdapter = (io) => {
1140
- const pubClient = redis.duplicate();
1141
- const subClient = redis.duplicate();
758
+ var attachSocketRedisAdapter = (io, options = {}) => {
759
+ const pubClient = options.pubClient ?? redis.duplicate();
760
+ const subClient = options.subClient ?? redis.duplicate();
1142
761
  pubClient.on("error", (err) => {
1143
762
  getLogger().error("[socket-redis-adapter] pub client error", err);
1144
763
  });
1145
764
  subClient.on("error", (err) => {
1146
765
  getLogger().error("[socket-redis-adapter] sub client error", err);
1147
766
  });
1148
- io.adapter(createAdapter(pubClient, subClient));
767
+ io.adapter(createAdapter(pubClient, subClient, options.adapterOptions));
1149
768
  };
1150
769
 
1151
770
  // src/bootUuid.ts
@@ -1168,9 +787,32 @@ var readBootUuid = async (envKey) => {
1168
787
  };
1169
788
 
1170
789
  // src/synchronizedEnvHashes.ts
1171
- import { createHash } from "crypto";
1172
- var hashValue = (value) => {
1173
- return createHash("sha256").update(value).digest("hex");
790
+ import { createHash, createHmac } from "crypto";
791
+ var plainHash = (value) => createHash("sha256").update(value).digest("hex");
792
+ var hashSynchronizedValueWith = (config, value) => {
793
+ const { mode, salt } = config;
794
+ if (mode === "plain" || salt.length === 0) return plainHash(value);
795
+ if (mode === "salted") return createHash("sha256").update(salt).update(value).digest("hex");
796
+ return createHmac("sha256", salt).update(value).digest("hex");
797
+ };
798
+ var resolveHealthHashConfig = (bootUuid) => {
799
+ const { mode, salt } = getProjectConfig().http.healthHash;
800
+ if (salt === "@bootUuid") {
801
+ if ((mode === "salted" || mode === "hmac") && bootUuid && bootUuid.length > 0) {
802
+ return { mode, salt: bootUuid };
803
+ }
804
+ return { mode: "plain", salt: "" };
805
+ }
806
+ return { mode, salt };
807
+ };
808
+ var describeHealthHashConfig = () => {
809
+ const { mode, salt } = getProjectConfig().http.healthHash;
810
+ return { mode, bootUuidSalt: salt === "@bootUuid" };
811
+ };
812
+ var resolveHealthHashConfigFromDescriptor = (descriptor, bootUuid) => {
813
+ if (descriptor.mode === "plain") return { mode: "plain", salt: "" };
814
+ if (descriptor.bootUuidSalt) return { mode: descriptor.mode, salt: bootUuid };
815
+ return null;
1174
816
  };
1175
817
  var collectSynchronizedEnvKeys = () => {
1176
818
  const keys = /* @__PURE__ */ new Set();
@@ -1181,16 +823,17 @@ var collectSynchronizedEnvKeys = () => {
1181
823
  }
1182
824
  return [...keys].toSorted();
1183
825
  };
1184
- var computeSynchronizedEnvHashes = () => {
826
+ var computeSynchronizedEnvHashes = (bootUuid) => {
1185
827
  const keys = collectSynchronizedEnvKeys();
828
+ const config = resolveHealthHashConfig(bootUuid);
1186
829
  const out = {};
1187
830
  for (const key of keys) {
1188
831
  const value = process.env[key];
1189
- out[key] = value === void 0 ? null : hashValue(value);
832
+ out[key] = value === void 0 ? null : hashSynchronizedValueWith(config, value);
1190
833
  }
1191
834
  return out;
1192
835
  };
1193
- var hashSynchronizedValue = (value) => hashValue(value);
836
+ var hashSynchronizedValue = (value, bootUuid) => hashSynchronizedValueWith(resolveHealthHashConfig(bootUuid), value);
1194
837
 
1195
838
  // src/consoleLog.ts
1196
839
  var COLORS = {
@@ -1226,13 +869,10 @@ var initConsolelog = () => {
1226
869
  }
1227
870
  const extractedInfo = extractFrameLabel(frame);
1228
871
  let colorCode = COLORS.white;
1229
- for (const key of Object.keys(COLORS)) {
1230
- const index = args.indexOf(key);
1231
- if (index !== -1) {
1232
- colorCode = COLORS[key];
1233
- args.splice(index, 1);
1234
- break;
1235
- }
872
+ const lastArg = args.at(-1);
873
+ if (typeof lastArg === "string" && Object.prototype.hasOwnProperty.call(COLORS, lastArg)) {
874
+ colorCode = COLORS[lastArg];
875
+ args.pop();
1236
876
  }
1237
877
  if (typeof args[0] === "object") {
1238
878
  originalLog(`${colorCode}${extractedInfo}${COLORS.reset}`);
@@ -1243,33 +883,6 @@ var initConsolelog = () => {
1243
883
  };
1244
884
  };
1245
885
 
1246
- // src/redactedLogKeys.ts
1247
- var DEFAULT_REDACTED_LOG_KEYS = [
1248
- "password",
1249
- "confirmpassword",
1250
- "token",
1251
- "newtoken",
1252
- "authorization",
1253
- "cookie",
1254
- "set-cookie"
1255
- ];
1256
- var redactedKeys = new Set(DEFAULT_REDACTED_LOG_KEYS.map((key) => key.toLowerCase()));
1257
- var registerRedactedLogKeys = (keys) => {
1258
- for (const key of keys) {
1259
- redactedKeys.add(key.toLowerCase());
1260
- }
1261
- };
1262
- var getRedactedLogKeys = () => [...redactedKeys];
1263
- var isRedactedLogKey = (key) => {
1264
- return redactedKeys.has(key.toLowerCase());
1265
- };
1266
- var resetRedactedLogKeysForTests = () => {
1267
- redactedKeys.clear();
1268
- for (const key of DEFAULT_REDACTED_LOG_KEYS) {
1269
- redactedKeys.add(key.toLowerCase());
1270
- }
1271
- };
1272
-
1273
886
  // src/cookies.ts
1274
887
  var escapeRegExp = (value) => {
1275
888
  return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
@@ -1294,52 +907,38 @@ var getCookieValue = (cookieHeader, cookieName) => {
1294
907
  var hasCookie = (cookieHeader, cookieName) => {
1295
908
  return getCookieValue(cookieHeader, cookieName) !== null;
1296
909
  };
1297
-
1298
- // src/httpApiUtils.ts
1299
- var inferHttpMethod = (apiName) => {
1300
- const nameParts = apiName.split("/");
1301
- const lastPart = nameParts.at(-1)?.toLowerCase() ?? "";
1302
- const methodName = /^v\d+$/.test(lastPart) ? (nameParts.at(-2) ?? "").toLowerCase() : lastPart;
1303
- if (methodName.startsWith("get") || methodName.startsWith("fetch") || methodName.startsWith("list")) {
1304
- return "GET";
910
+ var applyCookiePrefixConstraints = (baseName, prefix, secureOverride) => {
911
+ if (prefix === "__Host-") {
912
+ return { name: `__Host-${baseName}`, secure: true, forcePathRoot: true, forbidDomain: true };
1305
913
  }
1306
- if (methodName.startsWith("delete") || methodName.startsWith("remove")) {
1307
- return "DELETE";
914
+ if (prefix === "__Secure-") {
915
+ return { name: `__Secure-${baseName}`, secure: true, forcePathRoot: false, forbidDomain: false };
1308
916
  }
1309
- if (methodName.startsWith("update") || methodName.startsWith("edit") || methodName.startsWith("patch")) {
1310
- return "PUT";
1311
- }
1312
- return "POST";
1313
- };
1314
- var getEffectiveHttpMethod = (apiConfig, apiName) => {
1315
- return apiConfig.httpMethod ?? inferHttpMethod(apiName);
1316
- };
1317
- var isMethodAllowed = (requestMethod, allowedMethod) => {
1318
- return requestMethod === allowedMethod || requestMethod === "OPTIONS";
917
+ return { name: baseName, secure: secureOverride ?? false, forcePathRoot: false, forbidDomain: false };
1319
918
  };
1320
919
 
1321
920
  // src/paths.ts
1322
- import path from "path";
921
+ import path2 from "path";
1323
922
  import fs from "fs";
1324
923
  import { fileURLToPath } from "url";
1325
924
  var __filename = fileURLToPath(import.meta.url);
1326
- var __dirname = path.dirname(__filename);
925
+ var __dirname = path2.dirname(__filename);
1327
926
  var isWorkspaceRoot = (candidate) => {
1328
- return fs.existsSync(path.join(candidate, "package.json")) && (fs.existsSync(path.join(candidate, "tsconfig.json")) || fs.existsSync(path.join(candidate, "tsconfig.server.json")));
927
+ return fs.existsSync(path2.join(candidate, "package.json")) && (fs.existsSync(path2.join(candidate, "tsconfig.json")) || fs.existsSync(path2.join(candidate, "tsconfig.server.json")));
1329
928
  };
1330
929
  var findWorkspaceRoot = (startDir) => {
1331
- let current = path.resolve(startDir);
930
+ let current = path2.resolve(startDir);
1332
931
  while (true) {
1333
932
  if (isWorkspaceRoot(current)) {
1334
933
  return current;
1335
934
  }
1336
- const parent = path.dirname(current);
935
+ const parent = path2.dirname(current);
1337
936
  if (parent === current) return null;
1338
937
  current = parent;
1339
938
  }
1340
939
  };
1341
940
  var ROOT_DIR = findWorkspaceRoot(process.cwd()) ?? findWorkspaceRoot(__dirname) ?? process.cwd();
1342
- var resolveAgainstRoot = (relative) => path.isAbsolute(relative) ? relative : path.join(ROOT_DIR, relative);
941
+ var resolveAgainstRoot = (relative) => path2.isAbsolute(relative) ? relative : path2.join(ROOT_DIR, relative);
1343
942
  var getSrcDir = () => resolveAgainstRoot(getProjectConfig().paths.srcDir);
1344
943
  var getServerDir = () => resolveAgainstRoot(getProjectConfig().paths.serverDir);
1345
944
  var getSharedDir = () => resolveAgainstRoot(getProjectConfig().paths.sharedDir);
@@ -1364,17 +963,17 @@ var getServerFunctionsDir = () => {
1364
963
  var getGeneratedSocketTypesPath = () => resolveAgainstRoot(getProjectConfig().paths.generatedSocketTypes);
1365
964
  var getGeneratedApiSchemasPath = () => resolveAgainstRoot(getProjectConfig().paths.generatedApiSchemas);
1366
965
  var getGeneratedApiDocsPath = () => resolveAgainstRoot(getProjectConfig().paths.generatedApiDocs);
1367
- var SRC_DIR = path.join(ROOT_DIR, "src");
1368
- var SERVER_DIR = path.join(ROOT_DIR, "server");
1369
- var SHARED_DIR = path.join(ROOT_DIR, "shared");
1370
- var UPLOADS_DIR = path.join(ROOT_DIR, "uploads");
1371
- var PUBLIC_DIR = path.join(ROOT_DIR, "public");
1372
- var SERVER_FUNCTIONS_DIR = path.join(SERVER_DIR, "functions");
1373
- var GENERATED_SOCKET_TYPES_PATH = path.join(SRC_DIR, "_sockets", "apiTypes.generated.ts");
1374
- var GENERATED_API_SCHEMAS_PATH = path.join(SRC_DIR, "_sockets", "apiInputSchemas.generated.ts");
1375
- var GENERATED_API_DOCS_PATH = path.join(SRC_DIR, "docs", "apiDocs.generated.json");
966
+ var SRC_DIR = path2.join(ROOT_DIR, "src");
967
+ var SERVER_DIR = path2.join(ROOT_DIR, "server");
968
+ var SHARED_DIR = path2.join(ROOT_DIR, "shared");
969
+ var UPLOADS_DIR = path2.join(ROOT_DIR, "uploads");
970
+ var PUBLIC_DIR = path2.join(ROOT_DIR, "public");
971
+ var SERVER_FUNCTIONS_DIR = path2.join(SERVER_DIR, "functions");
972
+ var GENERATED_SOCKET_TYPES_PATH = path2.join(SRC_DIR, "_sockets", "apiTypes.generated.ts");
973
+ var GENERATED_API_SCHEMAS_PATH = path2.join(SRC_DIR, "_sockets", "apiInputSchemas.generated.ts");
974
+ var GENERATED_API_DOCS_PATH = path2.join(SRC_DIR, "docs", "apiDocs.generated.json");
1376
975
  var TSCONFIG_ALIAS_FILES = ["tsconfig.server.json", "tsconfig.client.json"];
1377
- var resolveFromRoot = (...segments) => path.join(ROOT_DIR, ...segments);
976
+ var resolveFromRoot = (...segments) => path2.join(ROOT_DIR, ...segments);
1378
977
 
1379
978
  // src/socketMiddlewareRegistry.ts
1380
979
  var middlewares = [];
@@ -1391,50 +990,30 @@ var applySocketMiddlewares = (io) => {
1391
990
  }
1392
991
  };
1393
992
 
1394
- // src/csrfConfig.ts
1395
- var DEFAULT_CSRF_CONFIG = {
1396
- cookieName: "csrf-token",
1397
- headerName: "x-csrf-token",
1398
- tokenLength: 32,
1399
- cookieOptions: {
1400
- sameSite: "lax",
1401
- secure: true,
1402
- httpOnly: false,
1403
- path: "/",
1404
- maxAgeMs: 24 * 60 * 60 * 1e3
1405
- }
1406
- };
1407
- var active = DEFAULT_CSRF_CONFIG;
1408
- var registerCsrfConfig = (input) => {
1409
- active = {
1410
- ...active,
1411
- ...input,
1412
- cookieOptions: { ...active.cookieOptions, ...input.cookieOptions }
1413
- };
1414
- };
1415
- var getCsrfConfig = () => active;
1416
- var resetCsrfConfigForTests = () => {
1417
- active = DEFAULT_CSRF_CONFIG;
1418
- };
1419
-
1420
993
  // src/serveAvatars.ts
1421
- import path2 from "path";
994
+ import path3 from "path";
1422
995
  import { access } from "fs/promises";
1423
996
  import fs2 from "fs";
997
+ import stream from "stream";
1424
998
 
1425
999
  // src/avatarConfig.ts
1426
1000
  var DEFAULT_AVATAR_CONFIG = {
1427
1001
  formats: [{ extension: "webp", contentType: "image/webp" }],
1428
1002
  cacheControl: "public, max-age=86400"
1429
1003
  };
1430
- var activeConfig3 = DEFAULT_AVATAR_CONFIG;
1431
- var registerAvatarConfig = (config) => {
1432
- activeConfig3 = {
1004
+ var registry8 = createRegistry(DEFAULT_AVATAR_CONFIG, {
1005
+ //? Bespoke merge (not a generic deep-merge): an empty `formats` array falls
1006
+ //? back to the default formats rather than wiping serving. Built fresh from
1007
+ //? the defaults each call, matching the historical behaviour.
1008
+ transform: (config) => ({
1433
1009
  formats: config.formats?.length ? config.formats : DEFAULT_AVATAR_CONFIG.formats,
1434
1010
  cacheControl: config.cacheControl ?? DEFAULT_AVATAR_CONFIG.cacheControl
1435
- };
1011
+ })
1012
+ });
1013
+ var registerAvatarConfig = (config) => {
1014
+ registry8.register(config);
1436
1015
  };
1437
- var getAvatarConfig = () => activeConfig3;
1016
+ var getAvatarConfig = () => registry8.get();
1438
1017
 
1439
1018
  // src/serveAvatars.ts
1440
1019
  var FILE_ID_REGEX = /^[A-Za-z0-9_-]{1,128}$/;
@@ -1443,22 +1022,33 @@ var serveAvatar = async ({
1443
1022
  res
1444
1023
  }) => {
1445
1024
  const uploadsFolder = getUploadsDir();
1446
- const fileId = path2.basename(routePath, path2.extname(routePath));
1025
+ const fileId = path3.basename(routePath, path3.extname(routePath));
1447
1026
  if (!fileId || !FILE_ID_REGEX.test(fileId)) {
1448
1027
  res.writeHead(404, { "Content-Type": "text/plain" });
1449
1028
  res.end("File not found");
1450
1029
  return;
1451
1030
  }
1031
+ const preDispatch = await dispatchHook("preAvatarServe", { routePath, fileId });
1032
+ if (preDispatch.stopped) {
1033
+ res.writeHead(404, { "Content-Type": "text/plain" });
1034
+ res.end("File not found");
1035
+ return;
1036
+ }
1452
1037
  const { formats, cacheControl } = getAvatarConfig();
1453
1038
  for (const { extension, contentType } of formats) {
1454
- const filePath = path2.join(uploadsFolder, `${fileId}.${extension}`);
1039
+ const filePath = path3.join(uploadsFolder, `${fileId}.${extension}`);
1455
1040
  const [accessError] = await tryCatch(() => access(filePath));
1456
1041
  if (accessError) continue;
1457
1042
  res.writeHead(200, {
1458
1043
  "Content-Type": contentType,
1459
1044
  "Cache-Control": cacheControl
1460
1045
  });
1461
- fs2.createReadStream(filePath).pipe(res);
1046
+ stream.pipeline(fs2.createReadStream(filePath), res, (pipelineError) => {
1047
+ if (pipelineError) {
1048
+ getLogger().debug("avatar: stream pipeline failed", { routePath, fileId, error: pipelineError });
1049
+ }
1050
+ });
1051
+ void dispatchHook("postAvatarServe", { routePath, fileId, extension, contentType });
1462
1052
  return;
1463
1053
  }
1464
1054
  getLogger().debug("avatar: file not found", { routePath, fileId });
@@ -1493,13 +1083,13 @@ var processUpload = async (input) => {
1493
1083
  };
1494
1084
 
1495
1085
  // src/bindAddress.ts
1496
- var registered2 = null;
1086
+ var registered = null;
1497
1087
  var registerBindAddress = (address) => {
1498
- registered2 = address;
1088
+ registered = address;
1499
1089
  };
1500
1090
  var getBindAddress = () => {
1501
- if (registered2) {
1502
- return { ip: registered2.ip, port: String(registered2.port) };
1091
+ if (registered) {
1092
+ return { ip: registered.ip, port: String(registered.port) };
1503
1093
  }
1504
1094
  return {
1505
1095
  ip: process.env.SERVER_IP ?? "127.0.0.1",
@@ -1508,124 +1098,177 @@ var getBindAddress = () => {
1508
1098
  };
1509
1099
 
1510
1100
  // src/getParams.ts
1101
+ var writeJsonErrorAndResolve = (res, status, errorCode, resolve) => {
1102
+ res.setHeader("Content-Type", "application/json");
1103
+ res.writeHead(status);
1104
+ res.end(JSON.stringify({
1105
+ status: "error",
1106
+ httpStatus: status,
1107
+ message: errorCode,
1108
+ errorCode
1109
+ }));
1110
+ resolve(null);
1111
+ };
1511
1112
  async function getParams({ method, req, res: _res, queryString }) {
1512
- if (method == "GET") {
1513
- return Object.fromEntries(new URLSearchParams(queryString || ""));
1113
+ if (method === "GET") {
1114
+ return Object.fromEntries(new URLSearchParams(queryString ?? ""));
1514
1115
  }
1515
- return new Promise((resolve, reject) => {
1516
- const contentType = req.headers["content-type"] || "";
1116
+ return new Promise((resolve) => {
1117
+ const contentType = req.headers["content-type"] ?? "";
1517
1118
  const contentLengthHeader = req.headers["content-length"];
1518
1119
  const declaredLength = typeof contentLengthHeader === "string" ? Number(contentLengthHeader) : Number.NaN;
1519
1120
  const maxBodyBytes = getProjectConfig().http.requestBodyMaxBytes;
1520
1121
  if (Number.isFinite(declaredLength) && declaredLength > maxBodyBytes) {
1521
- _res.setHeader("Content-Type", "application/json");
1522
- _res.writeHead(413);
1523
- _res.end(JSON.stringify({
1524
- status: "error",
1525
- httpStatus: 413,
1526
- message: "api.payloadTooLarge",
1527
- errorCode: "api.payloadTooLarge"
1528
- }));
1529
- resolve(null);
1122
+ writeJsonErrorAndResolve(_res, 413, "api.payloadTooLarge", resolve);
1123
+ req.destroy();
1530
1124
  return;
1531
1125
  }
1532
- let body = "";
1126
+ const chunks = [];
1533
1127
  let bodySize = 0;
1534
1128
  req.on("data", (chunk) => {
1129
+ if (_res.headersSent) return;
1535
1130
  bodySize += chunk.length;
1536
1131
  if (bodySize > maxBodyBytes) {
1537
- _res.setHeader("Content-Type", "application/json");
1538
- _res.writeHead(413);
1539
- _res.end(JSON.stringify({
1540
- status: "error",
1541
- httpStatus: 413,
1542
- message: "api.payloadTooLarge",
1543
- errorCode: "api.payloadTooLarge"
1544
- }));
1132
+ writeJsonErrorAndResolve(_res, 413, "api.payloadTooLarge", resolve);
1545
1133
  req.destroy();
1546
- resolve(null);
1547
1134
  return;
1548
1135
  }
1549
- body += chunk.toString();
1136
+ chunks.push(chunk);
1550
1137
  });
1551
- req.on("end", async () => {
1552
- if (contentType.startsWith("application/x-www-form-urlencoded")) {
1553
- const parseData = () => {
1554
- const data = new URLSearchParams(body);
1555
- return Object.fromEntries(data);
1556
- };
1557
- const [, response] = await tryCatch(parseData);
1558
- if (response) {
1559
- resolve(response);
1138
+ req.on("end", () => {
1139
+ void (async () => {
1140
+ const body = Buffer.concat(chunks).toString("utf8");
1141
+ if (contentType.startsWith("application/x-www-form-urlencoded")) {
1142
+ const parseData = () => {
1143
+ const data = new URLSearchParams(body);
1144
+ return Object.fromEntries(data);
1145
+ };
1146
+ const [, response] = await tryCatch(parseData);
1147
+ if (response) {
1148
+ resolve(response);
1149
+ return;
1150
+ }
1151
+ writeJsonErrorAndResolve(_res, 400, "api.invalidRequestFormat", resolve);
1560
1152
  return;
1561
1153
  }
1562
- _res.setHeader("Content-Type", "application/json");
1563
- _res.writeHead(400);
1564
- _res.end(JSON.stringify({
1565
- status: "error",
1566
- httpStatus: 400,
1567
- message: "api.invalidRequestFormat",
1568
- errorCode: "api.invalidRequestFormat"
1569
- }));
1570
- resolve(null);
1571
- return;
1572
- }
1573
- if (contentType.startsWith("application/json")) {
1574
- const parseData = () => {
1575
- return JSON.parse(body || "{}");
1576
- };
1577
- const [, response] = await tryCatch(parseData);
1578
- if (response && typeof response === "object" && !Array.isArray(response)) {
1579
- resolve(response);
1154
+ if (contentType.startsWith("application/json")) {
1155
+ const parseData = () => {
1156
+ return JSON.parse(body || "{}");
1157
+ };
1158
+ const [, response] = await tryCatch(parseData);
1159
+ if (response && typeof response === "object" && !Array.isArray(response)) {
1160
+ resolve(response);
1161
+ return;
1162
+ }
1163
+ writeJsonErrorAndResolve(_res, 400, "api.invalidRequestFormat", resolve);
1580
1164
  return;
1581
1165
  }
1582
- _res.setHeader("Content-Type", "application/json");
1583
- _res.writeHead(400);
1584
- _res.end(JSON.stringify({
1585
- status: "error",
1586
- httpStatus: 400,
1587
- message: "api.invalidRequestFormat",
1588
- errorCode: "api.invalidRequestFormat"
1589
- }));
1590
- resolve(null);
1591
- return;
1592
- }
1593
- _res.setHeader("Content-Type", "application/json");
1594
- _res.writeHead(415);
1595
- _res.end(JSON.stringify({
1596
- status: "error",
1597
- httpStatus: 415,
1598
- message: "api.unsupportedMediaType",
1599
- errorCode: "api.unsupportedMediaType"
1600
- }));
1601
- resolve(null);
1166
+ writeJsonErrorAndResolve(_res, 415, "api.unsupportedMediaType", resolve);
1167
+ })();
1602
1168
  });
1603
- req.on("error", (error) => {
1604
- reject(error);
1169
+ req.on("error", () => {
1170
+ resolve(null);
1605
1171
  });
1606
1172
  });
1607
1173
  }
1608
1174
 
1609
1175
  // src/extractToken.ts
1610
1176
  var extractTokenFromSocket = (socket2) => {
1177
+ const config = getProjectConfig();
1611
1178
  const cookie = socket2.handshake.headers.cookie;
1612
- const sessionToken = typeof socket2.handshake.auth?.token === "string" ? socket2.handshake.auth.token : null;
1613
- const cookieToken = getCookieValue(cookie, getProjectConfig().http.sessionCookieName);
1614
- if (getProjectConfig().session.basedToken) {
1179
+ const sessionToken = typeof socket2.handshake.auth.token === "string" ? socket2.handshake.auth.token : null;
1180
+ const cookieToken = getCookieValue(cookie, config.http.sessionCookieName);
1181
+ if (config.session.basedToken) {
1615
1182
  return sessionToken ?? cookieToken;
1616
1183
  }
1617
- return cookieToken ?? sessionToken;
1184
+ if (config.http.acceptBearerInCookieMode) {
1185
+ return cookieToken ?? sessionToken;
1186
+ }
1187
+ return cookieToken;
1618
1188
  };
1619
1189
 
1620
1190
  // src/extractTokenFromRequest.ts
1621
1191
  var extractTokenFromRequest = (req) => {
1622
- const authHeader = req.headers.authorization;
1192
+ const rawAuthHeader = req.headers.authorization;
1193
+ const authHeader = typeof rawAuthHeader === "string" ? rawAuthHeader : Array.isArray(rawAuthHeader) && typeof rawAuthHeader[0] === "string" ? rawAuthHeader[0] : void 0;
1623
1194
  const bearerToken = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
1624
- const cookieToken = getCookieValue(req.headers.cookie, getProjectConfig().http.sessionCookieName);
1625
- if (getProjectConfig().session.basedToken) {
1195
+ const config = getProjectConfig();
1196
+ const cookieToken = getCookieValue(req.headers.cookie, config.http.sessionCookieName);
1197
+ if (config.session.basedToken) {
1626
1198
  return bearerToken ?? cookieToken;
1627
1199
  }
1628
- return cookieToken ?? bearerToken;
1200
+ if (config.http.acceptBearerInCookieMode) {
1201
+ return cookieToken ?? bearerToken;
1202
+ }
1203
+ return cookieToken;
1204
+ };
1205
+
1206
+ // src/resolveClientIp.ts
1207
+ import { createHash as createHash2 } from "crypto";
1208
+ var UNKNOWN_CLIENT_IP = "unknown";
1209
+ var canonicalizeIp = (value) => {
1210
+ let ip = value.startsWith("::ffff:") ? value.slice("::ffff:".length) : value;
1211
+ const zoneIdx = ip.indexOf("%");
1212
+ if (zoneIdx !== -1) ip = ip.slice(0, zoneIdx);
1213
+ return ip.includes(":") ? ip.toLowerCase() : ip;
1214
+ };
1215
+ var firstHeaderValue = (raw) => {
1216
+ if (raw === void 0) return void 0;
1217
+ const single = Array.isArray(raw) ? raw[0] : raw;
1218
+ if (typeof single !== "string") return void 0;
1219
+ const trimmed = single.trim();
1220
+ return trimmed.length > 0 ? trimmed : void 0;
1221
+ };
1222
+ var forwardedForHops = (raw) => {
1223
+ if (raw === void 0) return [];
1224
+ const parts = Array.isArray(raw) ? raw : [raw];
1225
+ return parts.flatMap((part) => typeof part === "string" ? part.split(",") : []).map((hop) => hop.trim()).filter((hop) => hop.length > 0);
1226
+ };
1227
+ var resolveRaw = ({ rawAddress, headers, trustProxy, trustedProxyHopCount }) => {
1228
+ const rawFallback = rawAddress && rawAddress.length > 0 ? canonicalizeIp(rawAddress) : UNKNOWN_CLIENT_IP;
1229
+ if (!trustProxy) return rawFallback;
1230
+ const hops = forwardedForHops(headers["x-forwarded-for"]);
1231
+ if (hops.length > 0) {
1232
+ const skip = Math.max(1, Math.floor(trustedProxyHopCount));
1233
+ const index = Math.max(0, hops.length - skip);
1234
+ const chosen = hops[index];
1235
+ if (chosen && chosen.length > 0) return canonicalizeIp(chosen);
1236
+ }
1237
+ const realIp = firstHeaderValue(headers["x-real-ip"]);
1238
+ if (realIp) return canonicalizeIp(realIp);
1239
+ return rawFallback;
1240
+ };
1241
+ var isLoopbackIp = (ip) => {
1242
+ if (ip === UNKNOWN_CLIENT_IP) return true;
1243
+ const canonical = ip.startsWith("::ffff:") ? ip.slice("::ffff:".length) : ip;
1244
+ if (canonical === "::1") return true;
1245
+ return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(canonical);
1246
+ };
1247
+ var TOKEN_HASH_LENGTH = 32;
1248
+ var deriveTokenBucketId = (token) => createHash2("sha256").update(token).digest("hex").slice(0, TOKEN_HASH_LENGTH);
1249
+ var resolveClientIp = ({ rawAddress, headers, trustProxy = false, trustedProxyHopCount = 1 }) => {
1250
+ const resolved = resolveRaw({ rawAddress, headers, trustProxy, trustedProxyHopCount });
1251
+ if (resolved === UNKNOWN_CLIENT_IP) {
1252
+ getLogger().warn('rate-limit: client IP unresolved \u2014 keyed into the shared "unknown" bucket', { trustProxy });
1253
+ }
1254
+ return resolved;
1255
+ };
1256
+
1257
+ // src/sessionProviderRegistry.ts
1258
+ var provider = null;
1259
+ var registerSessionProvider = (next) => {
1260
+ provider = next;
1261
+ };
1262
+ var getRegisteredSessionProvider = () => provider;
1263
+ var isSessionProviderRegistered = () => provider !== null;
1264
+ var resetSessionProviderForTests = () => {
1265
+ provider = null;
1266
+ };
1267
+ var readSession = async (token) => provider ? provider.getSession(token) : null;
1268
+ var writeSession = async (token, data, newUser, options) => provider ? provider.saveSession(token, data, newUser, options) : { ok: true };
1269
+ var removeSession = async (token, options) => provider ? provider.deleteSession(token, options) : false;
1270
+ var performLogout = async (input) => {
1271
+ if (provider) await provider.logout(input);
1629
1272
  };
1630
1273
 
1631
1274
  // src/validateRequest.ts
@@ -1636,7 +1279,7 @@ var validateRequest = ({
1636
1279
  auth,
1637
1280
  user
1638
1281
  }) => {
1639
- if (!auth.additional) {
1282
+ if (!auth.additional || auth.additional.length === 0) {
1640
1283
  return { status: "success" };
1641
1284
  }
1642
1285
  const forbid = (key) => ({
@@ -1645,6 +1288,9 @@ var validateRequest = ({
1645
1288
  errorParams: [{ key: "key", value: key }],
1646
1289
  httpStatus: 403
1647
1290
  });
1291
+ if (user === null || user === void 0) {
1292
+ return forbid(auth.additional[0]?.key ?? "");
1293
+ }
1648
1294
  for (const condition of auth.additional) {
1649
1295
  if (!(condition.key in user)) {
1650
1296
  return {
@@ -1673,6 +1319,7 @@ var validateRequest = ({
1673
1319
  };
1674
1320
 
1675
1321
  // src/rateLimiter.ts
1322
+ var MAX_MEMORY_STORE_SIZE = 5e4;
1676
1323
  var rateLimitStore = /* @__PURE__ */ new Map();
1677
1324
  var getRedisPrefix = () => formatKey(`:${getProjectConfig().rateLimiting.redisKeyPrefix}`, "");
1678
1325
  var isRedisMode = () => getProjectConfig().rateLimiting.store === "redis";
@@ -1682,7 +1329,8 @@ var buildAllowedResult = (limit) => ({
1682
1329
  remaining: Math.max(0, limit),
1683
1330
  resetIn: 0
1684
1331
  });
1685
- var redisFallbackLogged = false;
1332
+ var REDIS_FALLBACK_LOG_COOLDOWN_MS = 6e4;
1333
+ var redisFallbackLastLoggedAt = 0;
1686
1334
  var RATE_LIMIT_INCREMENT_SCRIPT = `
1687
1335
  local current = redis.call('INCR', KEYS[1])
1688
1336
  if current == 1 then
@@ -1692,7 +1340,7 @@ local ttl = redis.call('PTTL', KEYS[1])
1692
1340
  return { current, ttl }
1693
1341
  `;
1694
1342
  var normalizeWindowMs = (windowMs) => {
1695
- if (!Number.isFinite(windowMs) || windowMs <= 0) {
1343
+ if (windowMs === void 0 || !Number.isFinite(windowMs) || windowMs <= 0) {
1696
1344
  return getProjectConfig().rateLimiting.windowMs;
1697
1345
  }
1698
1346
  return windowMs;
@@ -1701,12 +1349,19 @@ var getRedisRateLimitKey = (key) => `${getRedisPrefix()}:${key}`;
1701
1349
  var checkRateLimitInMemory = ({
1702
1350
  key,
1703
1351
  limit,
1704
- windowMs = 6e4
1352
+ //? No default here — `normalizeWindowMs` reads `projectConfig.rateLimiting.windowMs`
1353
+ //? when `windowMs` is undefined or non-finite, so a hardcoded `60_000` would
1354
+ //? shadow the configured value (CORE-N6).
1355
+ windowMs
1705
1356
  }) => {
1706
1357
  const safeWindowMs = normalizeWindowMs(windowMs);
1707
1358
  const now = Date.now();
1708
1359
  const entry = rateLimitStore.get(key);
1709
1360
  if (!entry || entry.resetAt < now) {
1361
+ if (rateLimitStore.size >= MAX_MEMORY_STORE_SIZE) {
1362
+ const oldest = rateLimitStore.keys().next().value;
1363
+ if (oldest !== void 0) rateLimitStore.delete(oldest);
1364
+ }
1710
1365
  rateLimitStore.set(key, { count: 1, resetAt: now + safeWindowMs });
1711
1366
  return { allowed: true, remaining: limit - 1, resetIn: Math.ceil(safeWindowMs / 1e3) };
1712
1367
  }
@@ -1720,7 +1375,8 @@ var checkRateLimitInMemory = ({
1720
1375
  var checkRateLimitInRedis = async ({
1721
1376
  key,
1722
1377
  limit,
1723
- windowMs = 6e4
1378
+ //? No default — same reasoning as `checkRateLimitInMemory` (CORE-N6).
1379
+ windowMs
1724
1380
  }) => {
1725
1381
  const safeWindowMs = normalizeWindowMs(windowMs);
1726
1382
  const redisKey = getRedisRateLimitKey(key);
@@ -1772,11 +1428,20 @@ var getRateLimitStatusInRedis = async (key, limit) => {
1772
1428
  resetIn: Math.ceil(ttlMs / 1e3)
1773
1429
  };
1774
1430
  };
1431
+ var getOnStoreError = () => getProjectConfig().rateLimiting.onStoreError;
1775
1432
  var logRedisFallback = () => {
1776
- if (redisFallbackLogged) return;
1777
- redisFallbackLogged = true;
1778
- getLogger().warn("[RateLimiter] Redis mode unavailable, falling back to in-memory mode");
1433
+ const now = Date.now();
1434
+ if (now - redisFallbackLastLoggedAt < REDIS_FALLBACK_LOG_COOLDOWN_MS) return;
1435
+ redisFallbackLastLoggedAt = now;
1436
+ getLogger().warn(
1437
+ getOnStoreError() === "deny" ? "[RateLimiter] Redis mode unavailable \u2014 denying request (onStoreError=deny)" : "[RateLimiter] Redis mode unavailable, falling back to in-memory mode (onStoreError=memory)"
1438
+ );
1779
1439
  };
1440
+ var buildDeniedResult = () => ({
1441
+ allowed: false,
1442
+ remaining: 0,
1443
+ resetIn: Math.ceil(getProjectConfig().rateLimiting.windowMs / 1e3)
1444
+ });
1780
1445
  var defaultRateLimitStrategy = {
1781
1446
  name: "default-memory-redis",
1782
1447
  async check(params) {
@@ -1785,6 +1450,7 @@ var defaultRateLimitStrategy = {
1785
1450
  const redisResult = await checkRateLimitInRedis(params);
1786
1451
  if (redisResult) return redisResult;
1787
1452
  logRedisFallback();
1453
+ if (getOnStoreError() === "deny") return buildDeniedResult();
1788
1454
  }
1789
1455
  return checkRateLimitInMemory(params);
1790
1456
  },
@@ -1794,6 +1460,7 @@ var defaultRateLimitStrategy = {
1794
1460
  const redisResult = await getRateLimitStatusInRedis(key, limit);
1795
1461
  if (redisResult) return redisResult;
1796
1462
  logRedisFallback();
1463
+ if (getOnStoreError() === "deny") return buildDeniedResult();
1797
1464
  }
1798
1465
  return getRateLimitStatusInMemory(key, limit);
1799
1466
  },
@@ -1821,16 +1488,32 @@ var defaultRateLimitStrategy = {
1821
1488
  rateLimitStore.clear();
1822
1489
  }
1823
1490
  };
1824
- var activeStrategy = defaultRateLimitStrategy;
1491
+ var strategyRegistry = createRegistry(defaultRateLimitStrategy, {
1492
+ onRegister: (strategy) => {
1493
+ getLogger().debug(`[RateLimiter] active strategy \u2192 ${strategy.name}`);
1494
+ }
1495
+ });
1825
1496
  var registerRateLimitStrategy = (strategy) => {
1826
- activeStrategy = strategy;
1827
- getLogger().debug(`[RateLimiter] active strategy \u2192 ${strategy.name}`);
1828
- };
1829
- var getRateLimitStrategy = () => activeStrategy;
1830
- var checkRateLimit = async (params) => activeStrategy.check(params);
1831
- var getRateLimitStatus = async (key, limit) => activeStrategy.getStatus(key, limit);
1832
- var clearRateLimit = async (key) => activeStrategy.clear(key);
1833
- var clearAllRateLimits = async () => activeStrategy.clearAll();
1497
+ strategyRegistry.register(strategy);
1498
+ };
1499
+ var getRateLimitStrategy = () => strategyRegistry.get();
1500
+ var checkRateLimit = async (params) => {
1501
+ ensureCleanupScheduled();
1502
+ return strategyRegistry.get().check(params);
1503
+ };
1504
+ var getRateLimitStatus = async (key, limit) => {
1505
+ ensureCleanupScheduled();
1506
+ return strategyRegistry.get().getStatus(key, limit);
1507
+ };
1508
+ var clearRateLimit = async (key) => strategyRegistry.get().clear(key);
1509
+ var clearAllRateLimits = async () => {
1510
+ if (process.env.NODE_ENV === "production") {
1511
+ getLogger().warn("[RateLimiter] clearAllRateLimits() called in production \u2014 ignored. Use clearRateLimit(key) for scoped resets.");
1512
+ return;
1513
+ }
1514
+ return strategyRegistry.get().clearAll();
1515
+ };
1516
+ var cleanupStarted = false;
1834
1517
  var scheduleCleanup = () => {
1835
1518
  const intervalMs = getProjectConfig().rateLimiting.cleanupIntervalMs;
1836
1519
  const timer = setTimeout(() => {
@@ -1849,7 +1532,16 @@ var scheduleCleanup = () => {
1849
1532
  }, intervalMs);
1850
1533
  timer.unref();
1851
1534
  };
1852
- scheduleCleanup();
1535
+ var ensureCleanupScheduled = () => {
1536
+ if (cleanupStarted) return;
1537
+ cleanupStarted = true;
1538
+ if (!isRedisMode() && getOnStoreError() === "deny") {
1539
+ getLogger().warn(
1540
+ '[RateLimiter] onStoreError="deny" has no effect in memory mode (only Redis mode has a fallback path). Switch to store:"redis" or remove the setting.'
1541
+ );
1542
+ }
1543
+ scheduleCleanup();
1544
+ };
1853
1545
 
1854
1546
  // src/runtimeTypeValidation.ts
1855
1547
  var splitTopLevel = (value, splitter) => {
@@ -1857,24 +1549,78 @@ var splitTopLevel = (value, splitter) => {
1857
1549
  let depthParen = 0;
1858
1550
  let depthBrace = 0;
1859
1551
  let depthBracket = 0;
1552
+ let inString = null;
1860
1553
  let token = "";
1861
- for (const char of value) {
1862
- if (char === "(") depthParen += 1;
1863
- if (char === ")") depthParen -= 1;
1864
- if (char === "{") depthBrace += 1;
1865
- if (char === "}") depthBrace -= 1;
1866
- if (char === "[") depthBracket += 1;
1867
- if (char === "]") depthBracket -= 1;
1868
- if (char === splitter && depthParen === 0 && depthBrace === 0 && depthBracket === 0) {
1869
- items.push(token.trim());
1870
- token = "";
1554
+ const codePoints = [...value];
1555
+ let i = 0;
1556
+ while (i < codePoints.length) {
1557
+ const char = codePoints[i] ?? "";
1558
+ i += 1;
1559
+ if (inString && char === "\\") {
1560
+ token += char;
1561
+ const next = codePoints[i] ?? "";
1562
+ token += next;
1563
+ i += 1;
1564
+ continue;
1565
+ }
1566
+ if (char === '"' || char === "'") {
1567
+ if (inString === char) {
1568
+ inString = null;
1569
+ } else {
1570
+ inString ??= char;
1571
+ }
1572
+ token += char;
1871
1573
  continue;
1872
1574
  }
1575
+ if (!inString) {
1576
+ if (char === "(") depthParen += 1;
1577
+ if (char === ")") depthParen -= 1;
1578
+ if (char === "{") depthBrace += 1;
1579
+ if (char === "}") depthBrace -= 1;
1580
+ if (char === "[") depthBracket += 1;
1581
+ if (char === "]") depthBracket -= 1;
1582
+ if (char === splitter && depthParen === 0 && depthBrace === 0 && depthBracket === 0) {
1583
+ items.push(token.trim());
1584
+ token = "";
1585
+ continue;
1586
+ }
1587
+ }
1873
1588
  token += char;
1874
1589
  }
1875
1590
  if (token.trim()) items.push(token.trim());
1876
1591
  return items;
1877
1592
  };
1593
+ var QUOTED_FIELD_RE = /^("|')((?:[^\\]|\\.)*?)\1(\?)?\s*:\s*([\s\S]+)$/;
1594
+ var PLAIN_FIELD_RE = /^([A-Za-z_][A-Za-z0-9_]*)(\?)?\s*:\s*([\s\S]+)$/;
1595
+ var INDEX_SIG_RE = /^\[\s*([A-Za-z_][A-Za-z0-9_]*)\s*:\s*([^\]]+)\]\s*:\s*([\s\S]+)$/;
1596
+ var parseFieldOrIndex = (trimmed, fields, indexSignatures) => {
1597
+ const quotedMatch = QUOTED_FIELD_RE.exec(trimmed);
1598
+ if (quotedMatch) {
1599
+ fields.push({
1600
+ key: (quotedMatch[2] ?? "").replaceAll(String.raw`\'`, "'").replaceAll(String.raw`\"`, '"'),
1601
+ optional: Boolean(quotedMatch[3]),
1602
+ type: (quotedMatch[4] ?? "").trim()
1603
+ });
1604
+ return;
1605
+ }
1606
+ const plainMatch = PLAIN_FIELD_RE.exec(trimmed);
1607
+ if (plainMatch) {
1608
+ fields.push({
1609
+ key: plainMatch[1] ?? "",
1610
+ optional: Boolean(plainMatch[2]),
1611
+ type: (plainMatch[3] ?? "").trim()
1612
+ });
1613
+ return;
1614
+ }
1615
+ const indexMatch = INDEX_SIG_RE.exec(trimmed);
1616
+ if (indexMatch) {
1617
+ indexSignatures.push({
1618
+ keyName: (indexMatch[1] ?? "").trim(),
1619
+ keyType: (indexMatch[2] ?? "").trim(),
1620
+ type: (indexMatch[3] ?? "").trim()
1621
+ });
1622
+ }
1623
+ };
1878
1624
  var parseObjectFields = (typeText) => {
1879
1625
  const clean = typeText.trim();
1880
1626
  if (!clean.startsWith("{") || !clean.endsWith("}")) {
@@ -1891,25 +1637,7 @@ var parseObjectFields = (typeText) => {
1891
1637
  if (char === ";" && depth === 0) {
1892
1638
  const trimmed = part.trim();
1893
1639
  if (trimmed) {
1894
- const fieldMatch = /^("|')?[A-Za-z_][A-Za-z0-9_]*("|')?(\?)?\s*:\s*([\s\S]+)$/.exec(trimmed);
1895
- if (fieldMatch) {
1896
- const keyMatch = /^("|')?[A-Za-z_][A-Za-z0-9_]*("|')?/.exec(trimmed);
1897
- const rawKey = keyMatch?.[0] ?? "";
1898
- fields.push({
1899
- key: rawKey.replaceAll(/^['"]|['"]$/g, ""),
1900
- optional: Boolean(fieldMatch[3]),
1901
- type: (fieldMatch[4] ?? "").trim()
1902
- });
1903
- } else {
1904
- const indexMatch = /^\[\s*([A-Za-z_][A-Za-z0-9_]*)\s*:\s*([^\]]+)\]\s*:\s*([\s\S]+)$/.exec(trimmed);
1905
- if (indexMatch) {
1906
- indexSignatures.push({
1907
- keyName: (indexMatch[1] ?? "").trim(),
1908
- keyType: (indexMatch[2] ?? "").trim(),
1909
- type: (indexMatch[3] ?? "").trim()
1910
- });
1911
- }
1912
- }
1640
+ parseFieldOrIndex(trimmed, fields, indexSignatures);
1913
1641
  }
1914
1642
  part = "";
1915
1643
  continue;
@@ -1918,25 +1646,7 @@ var parseObjectFields = (typeText) => {
1918
1646
  }
1919
1647
  const final = part.trim();
1920
1648
  if (final) {
1921
- const fieldMatch = /^("|')?[A-Za-z_][A-Za-z0-9_]*("|')?(\?)?\s*:\s*([\s\S]+)$/.exec(final);
1922
- if (fieldMatch) {
1923
- const keyMatch = /^("|')?[A-Za-z_][A-Za-z0-9_]*("|')?/.exec(final);
1924
- const rawKey = keyMatch?.[0] ?? "";
1925
- fields.push({
1926
- key: rawKey.replaceAll(/^['"]|['"]$/g, ""),
1927
- optional: Boolean(fieldMatch[3]),
1928
- type: (fieldMatch[4] ?? "").trim()
1929
- });
1930
- } else {
1931
- const indexMatch = /^\[\s*([A-Za-z_][A-Za-z0-9_]*)\s*:\s*([^\]]+)\]\s*:\s*([\s\S]+)$/.exec(final);
1932
- if (indexMatch) {
1933
- indexSignatures.push({
1934
- keyName: (indexMatch[1] ?? "").trim(),
1935
- keyType: (indexMatch[2] ?? "").trim(),
1936
- type: (indexMatch[3] ?? "").trim()
1937
- });
1938
- }
1939
- }
1649
+ parseFieldOrIndex(final, fields, indexSignatures);
1940
1650
  }
1941
1651
  return { fields, indexSignatures };
1942
1652
  };
@@ -1961,78 +1671,120 @@ var isPrimitiveMatch = (type, value) => {
1961
1671
  if (type === "false") return value === false;
1962
1672
  if (type === "null") return value === null;
1963
1673
  if (type === "undefined") return value === void 0;
1964
- if (type === "Date") return typeof value === "string" || value instanceof Date;
1674
+ if (type === "Date") return value instanceof Date || typeof value === "string" && !Number.isNaN(Date.parse(value));
1965
1675
  return false;
1966
1676
  };
1967
1677
  var isPrimitiveType = (type) => {
1968
1678
  return ["string", "number", "boolean", "true", "false", "null", "undefined", "Date"].includes(type);
1969
1679
  };
1970
- var validateType = (typeText, value, path3) => {
1680
+ var PROTO_POLLUTION_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1681
+ var parseRecordParts = (type) => {
1682
+ const inner = type.slice("Record<".length, -1);
1683
+ let depth = 0;
1684
+ for (let index = 0; index < inner.length; index += 1) {
1685
+ const char = inner[index];
1686
+ if (char === "<" || char === "{" || char === "[" || char === "(") depth += 1;
1687
+ else if (char === ">" || char === "}" || char === "]" || char === ")") depth -= 1;
1688
+ else if (char === "," && depth === 0) {
1689
+ return {
1690
+ keyType: inner.slice(0, index).trim(),
1691
+ valueType: inner.slice(index + 1).trim()
1692
+ };
1693
+ }
1694
+ }
1695
+ return null;
1696
+ };
1697
+ var MAX_VALIDATION_DEPTH = 64;
1698
+ var validateType = (typeText, value, path4, depth = 0) => {
1699
+ if (depth > MAX_VALIDATION_DEPTH) {
1700
+ return { status: "error", message: `${path4}: input nesting exceeds the maximum depth of ${String(MAX_VALIDATION_DEPTH)}` };
1701
+ }
1971
1702
  const type = typeText.trim();
1972
1703
  if (type.startsWith("__RUNTIME_UNRESOLVED__::")) {
1973
1704
  const unresolvedMessage = type.replace("__RUNTIME_UNRESOLVED__::", "").trim();
1974
- return { status: "error", message: `${path3}: ${unresolvedMessage}` };
1705
+ return { status: "error", message: `${path4}: ${unresolvedMessage}` };
1975
1706
  }
1976
1707
  if (type.startsWith("(") && type.endsWith(")")) {
1977
- return validateType(type.slice(1, -1), value, path3);
1708
+ return validateType(type.slice(1, -1), value, path4, depth + 1);
1978
1709
  }
1979
1710
  if (type.includes("|")) {
1980
1711
  const unionParts = splitTopLevel(type, "|").filter(Boolean);
1981
1712
  if (unionParts.length > 1) {
1982
1713
  for (const unionType of unionParts) {
1983
- const result = validateType(unionType, value, path3);
1714
+ const result = validateType(unionType, value, path4, depth + 1);
1984
1715
  if (result.status === "success") return result;
1985
1716
  }
1986
- return { status: "error", message: `${path3} does not match union type ${type}` };
1717
+ return { status: "error", message: `${path4} does not match union type ${type}` };
1718
+ }
1719
+ if (unionParts.length === 1) {
1720
+ return validateType(unionParts[0] ?? type, value, path4, depth + 1);
1987
1721
  }
1988
1722
  }
1989
1723
  if (type.includes("&")) {
1990
1724
  const intersectionParts = splitTopLevel(type, "&").filter(Boolean);
1991
1725
  if (intersectionParts.length > 1) {
1992
1726
  for (const intersectionType of intersectionParts) {
1993
- const result = validateType(intersectionType, value, path3);
1727
+ const result = validateType(intersectionType, value, path4, depth + 1);
1994
1728
  if (result.status === "error") return result;
1995
1729
  }
1996
1730
  return { status: "success" };
1997
1731
  }
1732
+ if (intersectionParts.length === 1) {
1733
+ return validateType(intersectionParts[0] ?? type, value, path4, depth + 1);
1734
+ }
1998
1735
  }
1999
1736
  if (type.endsWith("[]")) {
2000
1737
  if (!Array.isArray(value)) {
2001
- return { status: "error", message: `${path3} should be an array` };
1738
+ return { status: "error", message: `${path4} should be an array` };
2002
1739
  }
2003
1740
  const itemType = type.slice(0, -2).trim();
2004
- if (itemType === type) {
2005
- return { status: "success" };
1741
+ if (itemType === "" || itemType === type) {
1742
+ return { status: "error", message: `${path4}: unvalidatable array element type for ${type}` };
2006
1743
  }
2007
1744
  for (const [index, element] of value.entries()) {
2008
- const result = validateType(itemType, element, `${path3}[${String(index)}]`);
1745
+ const result = validateType(itemType, element, `${path4}[${String(index)}]`, depth + 1);
2009
1746
  if (result.status === "error") return result;
2010
1747
  }
2011
1748
  return { status: "success" };
2012
1749
  }
2013
1750
  if (type.startsWith("'") && type.endsWith("'") || type.startsWith('"') && type.endsWith('"')) {
2014
1751
  const literal = type.slice(1, -1);
2015
- return value === literal ? { status: "success" } : { status: "error", message: `${path3} should equal ${literal}` };
1752
+ return value === literal ? { status: "success" } : { status: "error", message: `${path4} should equal ${literal}` };
2016
1753
  }
2017
1754
  if (isPrimitiveMatch(type, value)) {
2018
1755
  return { status: "success" };
2019
1756
  }
2020
1757
  if (isPrimitiveType(type)) {
2021
1758
  const expectedType = type === "Date" ? "Date (ISO string or Date)" : type;
2022
- return { status: "error", message: `${path3} should be ${expectedType}` };
1759
+ return { status: "error", message: `${path4} should be ${expectedType}` };
2023
1760
  }
2024
1761
  if (type === "any" || type === "unknown") {
2025
1762
  return { status: "success" };
2026
1763
  }
2027
1764
  if (/^Record<.+>$/.test(type)) {
2028
- if (typeof value === "object" && value !== null && !Array.isArray(value)) {
2029
- return { status: "success" };
1765
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
1766
+ return { status: "error", message: `${path4} should be an object` };
2030
1767
  }
2031
- return { status: "error", message: `${path3} should be an object` };
1768
+ const record = value;
1769
+ const recordParts = parseRecordParts(type);
1770
+ if (!recordParts) {
1771
+ return { status: "error", message: `${path4}: unvalidatable Record type ${type} \u2014 model it explicitly` };
1772
+ }
1773
+ for (const key of Object.keys(record)) {
1774
+ if (PROTO_POLLUTION_KEYS.has(key)) {
1775
+ return { status: "error", message: `${path4}.${key} is not allowed` };
1776
+ }
1777
+ if ((recordParts.keyType === "number" || recordParts.keyType.includes("|") || recordParts.keyType.startsWith("'") || recordParts.keyType.startsWith('"')) && !matchesIndexKeyType({ keyType: recordParts.keyType, key })) {
1778
+ return { status: "error", message: `${path4}.${key} is not allowed` };
1779
+ }
1780
+ const valueResult = validateType(recordParts.valueType, record[key], `${path4}.${key}`, depth + 1);
1781
+ if (valueResult.status === "error") return valueResult;
1782
+ }
1783
+ return { status: "success" };
2032
1784
  }
2033
1785
  if (type.startsWith("{") && type.endsWith("}")) {
2034
1786
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
2035
- return { status: "error", message: `${path3} should be an object` };
1787
+ return { status: "error", message: `${path4} should be an object` };
2036
1788
  }
2037
1789
  const { fields, indexSignatures } = parseObjectFields(type);
2038
1790
  const input = value;
@@ -2040,9 +1792,9 @@ var validateType = (typeText, value, path3) => {
2040
1792
  const fieldValue = input[field.key];
2041
1793
  if (fieldValue === void 0) {
2042
1794
  if (field.optional) continue;
2043
- return { status: "error", message: `${path3}.${field.key} is required` };
1795
+ return { status: "error", message: `${path4}.${field.key} is required` };
2044
1796
  }
2045
- const result = validateType(field.type, fieldValue, `${path3}.${field.key}`);
1797
+ const result = validateType(field.type, fieldValue, `${path4}.${field.key}`, depth + 1);
2046
1798
  if (result.status === "error") return result;
2047
1799
  }
2048
1800
  const allowedKeys = new Set(fields.map((field) => field.key));
@@ -2050,8 +1802,11 @@ var validateType = (typeText, value, path3) => {
2050
1802
  if (allowedKeys.has(key)) {
2051
1803
  continue;
2052
1804
  }
1805
+ if (PROTO_POLLUTION_KEYS.has(key)) {
1806
+ return { status: "error", message: `${path4}.${key} is not allowed` };
1807
+ }
2053
1808
  if (indexSignatures.length === 0) {
2054
- return { status: "error", message: `${path3}.${key} is not allowed` };
1809
+ return { status: "error", message: `${path4}.${key} is not allowed` };
2055
1810
  }
2056
1811
  const indexValue = input[key];
2057
1812
  let matched = false;
@@ -2059,25 +1814,32 @@ var validateType = (typeText, value, path3) => {
2059
1814
  if (!matchesIndexKeyType({ keyType: indexSignature.keyType, key })) {
2060
1815
  continue;
2061
1816
  }
2062
- const indexResult = validateType(indexSignature.type, indexValue, `${path3}.${key}`);
1817
+ const indexResult = validateType(indexSignature.type, indexValue, `${path4}.${key}`, depth + 1);
2063
1818
  if (indexResult.status === "success") {
2064
1819
  matched = true;
2065
1820
  break;
2066
1821
  }
2067
1822
  }
2068
1823
  if (!matched) {
2069
- return { status: "error", message: `${path3}.${key} is not allowed` };
1824
+ return { status: "error", message: `${path4}.${key} is not allowed` };
2070
1825
  }
2071
1826
  }
2072
1827
  return { status: "success" };
2073
1828
  }
2074
1829
  if (/^[A-Za-z_][A-Za-z0-9_]*(?:<.+>)?$/.test(type)) {
2075
1830
  if (/^[A-Za-z_][A-Za-z0-9_]*<.+>$/.test(type)) {
2076
- return { status: "error", message: `${path3}: unresolved utility ${type}` };
1831
+ return { status: "error", message: `${path4}: unresolved utility ${type}` };
2077
1832
  }
2078
- return { status: "error", message: `${path3}: unresolved type ${type}` };
1833
+ return { status: "error", message: `${path4}: unresolved type ${type}` };
2079
1834
  }
2080
- return { status: "success" };
1835
+ return { status: "error", message: `${path4}: unvalidatable type ${type} \u2014 model it explicitly or set validation: 'relaxed' on the route` };
1836
+ };
1837
+ var safeValidateType = (typeText, value, path4) => {
1838
+ const [error, result] = tryCatchSync(() => validateType(typeText, value, path4));
1839
+ if (error || !result) {
1840
+ return { status: "error", message: `${path4}: input validation failed` };
1841
+ }
1842
+ return result;
2081
1843
  };
2082
1844
  var validateInputByType = async ({
2083
1845
  typeText,
@@ -2089,7 +1851,11 @@ var validateInputByType = async ({
2089
1851
  return { status: "success" };
2090
1852
  }
2091
1853
  if (process.env.NODE_ENV === "production") {
2092
- return { status: "success" };
1854
+ const { getProjectConfig: getProjectConfig2 } = await import("./projectConfig-JX563K43.js");
1855
+ if (getProjectConfig2().validation.runtimeMode === "off") {
1856
+ return { status: "success" };
1857
+ }
1858
+ return safeValidateType(typeText, value, rootKey);
2093
1859
  }
2094
1860
  const devkitModuleId = "@luckystack/devkit";
2095
1861
  const devkit = await import(devkitModuleId);
@@ -2097,7 +1863,7 @@ var validateInputByType = async ({
2097
1863
  if (resolvedType.status === "error") {
2098
1864
  return { status: "error", message: `${rootKey}: ${resolvedType.message}` };
2099
1865
  }
2100
- return validateType(resolvedType.typeText, value, rootKey);
1866
+ return safeValidateType(resolvedType.typeText, value, rootKey);
2101
1867
  };
2102
1868
 
2103
1869
  // src/checkOrigin.ts
@@ -2114,24 +1880,25 @@ var normalizeOrigin = ({ value, secure }) => {
2114
1880
  return extractedOrigin.replace(/^http:\/\/(.+):80$/i, "http://$1").replace(/^https:\/\/(.+):443$/i, "https://$1");
2115
1881
  };
2116
1882
  var isLocalhostOrigin = (normalized) => {
2117
- return /^https?:\/\/localhost(:\d+)?$/i.test(normalized);
1883
+ return /^https?:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/i.test(normalized);
2118
1884
  };
2119
1885
  var allowedOrigin = (origin) => {
2120
1886
  const secure = process.env.SECURE === "true";
2121
1887
  const cors = getProjectConfig().http.cors;
2122
1888
  const configured = cors.allowedOrigins;
2123
1889
  const { ip: bindIp, port: bindPort } = getBindAddress();
2124
- const location = bindPort ? `http${secure ? "s" : ""}://${bindIp}:${bindPort}` : `http${secure ? "s" : ""}://${bindIp}`;
1890
+ const rawLocation = bindPort ? `http${secure ? "s" : ""}://${bindIp}:${bindPort}` : `http${secure ? "s" : ""}://${bindIp}`;
1891
+ const location = normalizeOrigin({ value: rawLocation, secure });
2125
1892
  const normalizedOrigin = normalizeOrigin({ value: origin, secure });
2126
1893
  if (cors.allowLocalhost && normalizedOrigin && isLocalhostOrigin(normalizedOrigin)) {
2127
1894
  return true;
2128
1895
  }
2129
1896
  if (typeof configured === "function") {
2130
- if (normalizedOrigin === location) return true;
2131
- if (configured(origin)) return true;
1897
+ if (normalizedOrigin && normalizedOrigin === location) return true;
1898
+ if (configured(normalizedOrigin)) return true;
2132
1899
  } else {
2133
1900
  const normalizedAllowedOrigins = new Set(
2134
- [location, ...configured].map((value) => normalizeOrigin({ value, secure })).filter(Boolean)
1901
+ [location, ...configured.map((value) => normalizeOrigin({ value, secure }))].filter(Boolean)
2135
1902
  );
2136
1903
  if (normalizedOrigin && normalizedAllowedOrigins.has(normalizedOrigin)) {
2137
1904
  return true;
@@ -2156,121 +1923,6 @@ var allowedOrigin = (origin) => {
2156
1923
  };
2157
1924
  var checkOrigin_default = allowedOrigin;
2158
1925
 
2159
- // src/offlineQueue.ts
2160
- var apiQueue = [];
2161
- var syncQueue = [];
2162
- var isFlushingApi = false;
2163
- var isFlushingSync = false;
2164
- var isOnline = () => {
2165
- if (typeof navigator === "undefined") return true;
2166
- return navigator.onLine;
2167
- };
2168
- var evictExpired = (queue) => {
2169
- const maxAgeMs = getProjectConfig().offlineQueue.maxAgeMs;
2170
- if (maxAgeMs <= 0) return;
2171
- const cutoff = Date.now() - maxAgeMs;
2172
- for (let i = queue.length - 1; i >= 0; i -= 1) {
2173
- const item = queue[i];
2174
- if (item && item.createdAt < cutoff) {
2175
- queue.splice(i, 1);
2176
- }
2177
- }
2178
- };
2179
- var enqueueWithPolicy = (queue, item, label) => {
2180
- evictExpired(queue);
2181
- const { maxSize, dropPolicy: globalPolicy } = getProjectConfig().offlineQueue;
2182
- const effectivePolicy = item.dropPolicy ?? globalPolicy;
2183
- if (queue.length < maxSize) {
2184
- queue.push(item);
2185
- return true;
2186
- }
2187
- if (effectivePolicy === "drop-oldest") {
2188
- queue.shift();
2189
- queue.push(item);
2190
- getLogger().debug(`offlineQueue:${label} full \u2014 dropped oldest item (policy=${effectivePolicy})`);
2191
- return true;
2192
- }
2193
- if (effectivePolicy === "drop-newest") {
2194
- getLogger().debug(`offlineQueue:${label} full \u2014 dropped newest item (policy=${effectivePolicy})`);
2195
- return false;
2196
- }
2197
- getLogger().warn(`offlineQueue:${label} full \u2014 rejecting enqueue (policy=${effectivePolicy})`);
2198
- return false;
2199
- };
2200
- var enqueueApiRequest = (item) => enqueueWithPolicy(apiQueue, item, "api");
2201
- var enqueueSyncRequest = (item) => enqueueWithPolicy(syncQueue, item, "sync");
2202
- var removeApiQueueItem = (id) => {
2203
- const index = apiQueue.findIndex((item) => item.id === id);
2204
- if (index !== -1) {
2205
- apiQueue.splice(index, 1);
2206
- }
2207
- };
2208
- var removeSyncQueueItem = (id) => {
2209
- const index = syncQueue.findIndex((item) => item.id === id);
2210
- if (index !== -1) {
2211
- syncQueue.splice(index, 1);
2212
- }
2213
- };
2214
- var removeApiQueueItemsByKey = (key) => {
2215
- for (let i = apiQueue.length - 1; i >= 0; i -= 1) {
2216
- if (apiQueue[i]?.key === key) {
2217
- apiQueue.splice(i, 1);
2218
- }
2219
- }
2220
- };
2221
- var flushApiQueue = (canRun, socketInstance) => {
2222
- if (isFlushingApi) return;
2223
- evictExpired(apiQueue);
2224
- if (apiQueue.length === 0) return;
2225
- if (!canRun()) return;
2226
- isFlushingApi = true;
2227
- while (apiQueue.length > 0) {
2228
- if (!canRun()) break;
2229
- const item = apiQueue.shift();
2230
- item?.run(socketInstance);
2231
- }
2232
- isFlushingApi = false;
2233
- };
2234
- var flushSyncQueue = (canRun, socketInstance) => {
2235
- if (isFlushingSync) return;
2236
- evictExpired(syncQueue);
2237
- if (syncQueue.length === 0) return;
2238
- if (!canRun()) return;
2239
- isFlushingSync = true;
2240
- while (syncQueue.length > 0) {
2241
- if (!canRun()) break;
2242
- const item = syncQueue.shift();
2243
- item?.run(socketInstance);
2244
- }
2245
- isFlushingSync = false;
2246
- };
2247
- var getApiQueueSize = () => apiQueue.length;
2248
- var getSyncQueueSize = () => syncQueue.length;
2249
-
2250
- // src/socketState.ts
2251
- var socket = null;
2252
- var setSocket = (next) => {
2253
- socket = next;
2254
- };
2255
- var responseIndex = 0;
2256
- var WAIT_FOR_SOCKET_INTERVAL_MS = 10;
2257
- var WAIT_FOR_SOCKET_MAX_ITERATIONS = 500;
2258
- var incrementResponseIndex = () => {
2259
- responseIndex = responseIndex + 1;
2260
- return responseIndex;
2261
- };
2262
- var waitForSocket = async () => {
2263
- let i = 0;
2264
- while (!socket) {
2265
- await new Promise((resolve) => setTimeout(resolve, WAIT_FOR_SOCKET_INTERVAL_MS));
2266
- i++;
2267
- if (i > WAIT_FOR_SOCKET_MAX_ITERATIONS) {
2268
- return null;
2269
- }
2270
- }
2271
- return socket;
2272
- };
2273
-
2274
1926
  // src/cancelRegistry.ts
2275
1927
  var syncControllers = /* @__PURE__ */ new Map();
2276
1928
  var apiControllers = /* @__PURE__ */ new Map();
@@ -2291,16 +1943,16 @@ var abortSyncByCb = (socketId, cb) => {
2291
1943
  syncControllers.delete(key);
2292
1944
  return true;
2293
1945
  };
2294
- var registerApiAbortController = (socketId, responseIndex2, controller) => {
2295
- const key = buildKey(socketId, responseIndex2);
1946
+ var registerApiAbortController = (socketId, responseIndex, controller) => {
1947
+ const key = buildKey(socketId, responseIndex);
2296
1948
  apiControllers.set(key, controller);
2297
1949
  return key;
2298
1950
  };
2299
1951
  var unregisterApiAbortController = (key) => {
2300
1952
  apiControllers.delete(key);
2301
1953
  };
2302
- var abortApiByResponseIndex = (socketId, responseIndex2) => {
2303
- const key = buildKey(socketId, responseIndex2);
1954
+ var abortApiByResponseIndex = (socketId, responseIndex) => {
1955
+ const key = buildKey(socketId, responseIndex);
2304
1956
  const controller = apiControllers.get(key);
2305
1957
  if (!controller) return false;
2306
1958
  controller.abort();
@@ -2323,90 +1975,40 @@ var abortAllForSocket = (socketId) => {
2323
1975
  }
2324
1976
  };
2325
1977
 
2326
- // src/csrf.ts
2327
- var cachedToken = null;
2328
- var inflightFetch = null;
2329
- var resolveBackendUrl = () => {
2330
- if (globalThis.window !== void 0 && globalThis.location?.origin) {
2331
- return globalThis.location.origin;
2332
- }
2333
- return "";
2334
- };
2335
- var fetchCsrfToken = async () => {
2336
- const base = resolveBackendUrl();
2337
- const [error, token] = await tryCatch(async () => {
2338
- const response = await fetch(`${base}/auth/csrf`, {
2339
- method: "GET",
2340
- credentials: "include"
2341
- });
2342
- if (!response.ok) return null;
2343
- const body = await response.json();
2344
- return body.csrfToken ?? null;
2345
- });
2346
- if (error) return null;
2347
- return token ?? null;
2348
- };
2349
- var getCsrfToken = async () => {
2350
- if (getProjectConfig().session.basedToken) {
2351
- return null;
2352
- }
2353
- if (cachedToken) return cachedToken;
2354
- if (inflightFetch) return inflightFetch;
2355
- inflightFetch = fetchCsrfToken().then((token) => {
2356
- cachedToken = token;
2357
- inflightFetch = null;
2358
- return token;
2359
- });
2360
- return inflightFetch;
2361
- };
2362
- var clearCsrfToken = () => {
2363
- cachedToken = null;
2364
- inflightFetch = null;
2365
- };
2366
- var STATE_CHANGING = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
2367
- var isCsrfMismatchResponse = async (response) => {
2368
- if (response.status !== 403) return false;
2369
- const [error, isMismatch] = await tryCatch(async () => {
2370
- const cloned = response.clone();
2371
- const body = await cloned.json();
2372
- return body.errorCode === "auth.csrfMismatch";
2373
- });
2374
- if (error) return false;
2375
- return isMismatch ?? false;
2376
- };
2377
- var httpFetch = async (input, init = {}) => {
2378
- const method = (init.method ?? "GET").toUpperCase();
2379
- const headers = new Headers(init.headers);
2380
- if (!headers.has("Content-Type") && init.body && typeof init.body === "string") {
2381
- headers.set("Content-Type", "application/json");
2382
- }
2383
- const csrfHeaderName = getCsrfConfig().headerName;
2384
- const send = async (csrfToken) => {
2385
- const finalHeaders = new Headers(headers);
2386
- if (csrfToken && STATE_CHANGING.has(method) && !finalHeaders.has(csrfHeaderName)) {
2387
- finalHeaders.set(csrfHeaderName, csrfToken);
1978
+ // src/oneTimeToken.ts
1979
+ import { createHash as createHash3, randomBytes as randomBytes2 } from "crypto";
1980
+ var TOKEN_BYTES = 32;
1981
+ var hashToken = (token) => createHash3("sha256").update(token).digest("hex");
1982
+ var tokenKey = (namespace, token) => formatKey(namespace, hashToken(token));
1983
+ var issueOneTimeToken = (namespace, ttlSeconds, payload) => {
1984
+ const token = randomBytes2(TOKEN_BYTES).toString("hex");
1985
+ const value = typeof payload === "string" ? payload : JSON.stringify(payload);
1986
+ return {
1987
+ token,
1988
+ store: async () => {
1989
+ await redis_default.set(tokenKey(namespace, token), value, "EX", ttlSeconds);
2388
1990
  }
2389
- return fetch(input, {
2390
- ...init,
2391
- method,
2392
- headers: finalHeaders,
2393
- credentials: init.credentials ?? "include"
2394
- });
2395
1991
  };
2396
- if (!STATE_CHANGING.has(method) || getProjectConfig().session.basedToken) {
2397
- return send(null);
2398
- }
2399
- const token = await getCsrfToken();
2400
- let response = await send(token);
2401
- if (await isCsrfMismatchResponse(response)) {
2402
- clearCsrfToken();
2403
- const refreshed = await getCsrfToken();
2404
- if (refreshed) {
2405
- response = await send(refreshed);
2406
- }
2407
- }
2408
- return response;
2409
1992
  };
1993
+ var consumeOneTimeToken = async (namespace, token) => {
1994
+ if (!token || typeof token !== "string") return null;
1995
+ const key = tokenKey(namespace, token);
1996
+ const txResult = await redis_default.multi().get(key).del(key).exec();
1997
+ if (!txResult || txResult.length === 0) return null;
1998
+ const first = txResult[0];
1999
+ if (!first) return null;
2000
+ const [getErr, value] = first;
2001
+ if (getErr) return null;
2002
+ return typeof value === "string" && value.length > 0 ? value : null;
2003
+ };
2004
+ var consumeOneTimeTokenJson = async (namespace, token) => {
2005
+ const raw = await consumeOneTimeToken(namespace, token);
2006
+ if (raw === null) return null;
2007
+ const [parseErr, parsed] = tryCatchSync(() => JSON.parse(raw));
2008
+ if (parseErr) return null;
2009
+ return parsed;
2010
+ };
2011
+ var oneTimeTokenKey = tokenKey;
2410
2012
  export {
2411
2013
  BOOT_KEY_PREFIX,
2412
2014
  DEFAULT_AVATAR_CONFIG,
@@ -2415,23 +2017,29 @@ export {
2415
2017
  DEFAULT_ENV_FILES,
2416
2018
  DEFAULT_PAGE_ROUTE_RULES,
2417
2019
  DEFAULT_PROJECT_CONFIG,
2020
+ DEFAULT_REDACTED_LOG_KEYS,
2021
+ DEPTH_TRUNCATED_PLACEHOLDER,
2418
2022
  GENERATED_API_DOCS_PATH,
2419
2023
  GENERATED_API_SCHEMAS_PATH,
2420
2024
  GENERATED_SOCKET_TYPES_PATH,
2421
2025
  INVALID_ERROR_RESPONSE_CODE,
2422
2026
  PUBLIC_DIR,
2027
+ REDACTED_PLACEHOLDER,
2423
2028
  ROOT_DIR,
2424
2029
  SERVER_DIR,
2425
2030
  SERVER_FUNCTIONS_DIR,
2426
2031
  SHARED_DIR,
2427
2032
  SRC_DIR,
2428
2033
  TSCONFIG_ALIAS_FILES,
2034
+ UNKNOWN_CLIENT_IP,
2429
2035
  UPLOADS_DIR,
2430
2036
  abortAllForSocket,
2431
2037
  abortApiByResponseIndex,
2432
2038
  abortSyncByCb,
2433
2039
  acquireLease,
2434
2040
  checkOrigin_default as allowedOrigin,
2041
+ appendErrorTracker,
2042
+ applyCookiePrefixConstraints,
2435
2043
  applyErrorFormatter,
2436
2044
  applySocketMiddlewares,
2437
2045
  applyStrayKeyPrefix,
@@ -2456,22 +2064,33 @@ export {
2456
2064
  clearSocketMiddlewares,
2457
2065
  collectSynchronizedEnvKeys,
2458
2066
  computeSynchronizedEnvHashes,
2067
+ consumeOneTimeToken,
2068
+ consumeOneTimeTokenJson,
2459
2069
  createDevLogger,
2460
2070
  createLocalizedNormalizer,
2071
+ createRegistry,
2072
+ deepMerge,
2461
2073
  defaultHttpStatusForResponse,
2462
2074
  defaultRateLimitStrategy,
2463
2075
  defaultRedisKeyFormatter,
2076
+ defaultRoomNameFormatter,
2077
+ deriveTokenBucketId,
2078
+ describeHealthHashConfig,
2464
2079
  dispatchHook,
2465
2080
  dispatchSyncHook,
2466
2081
  enqueueApiRequest,
2467
2082
  enqueueSyncRequest,
2083
+ ensurePeerDepInstalled,
2468
2084
  env,
2085
+ escapeHtml,
2469
2086
  extractLanguageFromHeader,
2470
2087
  extractTokenFromRequest,
2471
2088
  extractTokenFromSocket,
2472
2089
  flushApiQueue,
2090
+ flushErrorTrackers,
2473
2091
  flushSyncQueue,
2474
2092
  formatKey,
2093
+ formatRoomName,
2475
2094
  getActiveErrorTrackers,
2476
2095
  getApiQueueSize,
2477
2096
  getAvatarConfig,
@@ -2479,10 +2098,12 @@ export {
2479
2098
  getCookieValue,
2480
2099
  getCsrfConfig,
2481
2100
  getCsrfToken,
2101
+ getCurrentErrorTrackerIdentity,
2482
2102
  getDeployConfig,
2483
2103
  getEffectiveHttpMethod,
2484
2104
  getEmailSender,
2485
2105
  getEmailSenderByName,
2106
+ getEnv,
2486
2107
  getEnvFiles,
2487
2108
  getErrorFormatter,
2488
2109
  getGeneratedApiDocsPath,
@@ -2509,6 +2130,8 @@ export {
2509
2130
  getRedisConnectionOptions,
2510
2131
  getRedisKeyFormatter,
2511
2132
  getRegisteredApiMethod,
2133
+ getRegisteredSessionProvider,
2134
+ getRoomNameFormatter,
2512
2135
  getRuntimeApiMaps,
2513
2136
  getRuntimeSyncMaps,
2514
2137
  getServerDir,
@@ -2522,6 +2145,7 @@ export {
2522
2145
  getUploadsDir,
2523
2146
  hasCookie,
2524
2147
  hashSynchronizedValue,
2148
+ hashSynchronizedValueWith,
2525
2149
  httpFetch,
2526
2150
  incrementResponseIndex,
2527
2151
  inferHttpMethod,
@@ -2532,10 +2156,13 @@ export {
2532
2156
  isEmailSenderRegistered,
2533
2157
  isErrorParamArray,
2534
2158
  isFalsy,
2159
+ isGetMethodName,
2535
2160
  isLocalizedNormalizerRegistered,
2536
2161
  isLoggerRegistered,
2162
+ isLoopbackIp,
2537
2163
  isMethodAllowed,
2538
2164
  isOnline,
2165
+ isPlainObject,
2539
2166
  isPrismaClientRegistered,
2540
2167
  isProduction,
2541
2168
  isProjectConfigRegistered,
@@ -2543,16 +2170,23 @@ export {
2543
2170
  isRedisClientRegistered,
2544
2171
  isRuntimeMapsProviderRegistered,
2545
2172
  isServicesConfigRegistered,
2173
+ isSessionProviderRegistered,
2174
+ issueOneTimeToken,
2546
2175
  listEmailSenderNames,
2547
2176
  loadEnvFiles,
2177
+ loadPeer,
2548
2178
  normalizeErrorResponse,
2549
2179
  normalizeErrorResponseCore,
2180
+ normalizeOrigin,
2550
2181
  notify,
2182
+ oneTimeTokenKey,
2551
2183
  parseServiceRouteName,
2552
2184
  parseTransportRouteName,
2185
+ performLogout,
2553
2186
  prisma,
2554
2187
  processUpload,
2555
2188
  readBootUuid,
2189
+ readSession,
2556
2190
  recordMetricAcrossTrackers,
2557
2191
  redis,
2558
2192
  registerApiAbortController,
@@ -2571,20 +2205,25 @@ export {
2571
2205
  registerLocalizedNormalizer,
2572
2206
  registerLogger,
2573
2207
  registerNotifier,
2208
+ registerPreCaptureFilter,
2574
2209
  registerPrismaClient,
2575
2210
  registerProjectConfig,
2576
2211
  registerRateLimitStrategy,
2577
2212
  registerRedactedLogKeys,
2578
2213
  registerRedisClient,
2579
2214
  registerRedisKeyFormatter,
2215
+ registerRoomNameFormatter,
2580
2216
  registerRuntimeMapsProvider,
2581
2217
  registerServicesConfig,
2218
+ registerSessionProvider,
2582
2219
  registerSocketMiddleware,
2220
+ registerStrayPrefixCommand,
2583
2221
  registerSyncAbortController,
2584
2222
  registerSyncHook,
2585
2223
  releaseLease,
2586
2224
  removeApiQueueItem,
2587
2225
  removeApiQueueItemsByKey,
2226
+ removeSession,
2588
2227
  removeSyncQueueItem,
2589
2228
  renewLease,
2590
2229
  resetClientsForTests,
@@ -2592,10 +2231,20 @@ export {
2592
2231
  resetLoggerForTests,
2593
2232
  resetRedactedLogKeysForTests,
2594
2233
  resetRedisKeyFormatterForTests,
2234
+ resetSessionProviderForTests,
2235
+ resolveClientIp,
2595
2236
  resolveEnvKey,
2596
2237
  resolveErrorMessage,
2597
2238
  resolveFromRoot,
2239
+ resolveHealthHashConfig,
2240
+ resolveHealthHashConfigFromDescriptor,
2241
+ runWithErrorTrackerIdentity,
2242
+ runWithErrorTrackerIdentityScope,
2243
+ sanitizeErrorString,
2244
+ sanitizeErrorStrings,
2245
+ sanitizeForLog,
2598
2246
  serveAvatar,
2247
+ setCurrentErrorTrackerIdentity,
2599
2248
  setErrorTrackerUser,
2600
2249
  setIoInstance,
2601
2250
  setSentryUser,
@@ -2605,13 +2254,16 @@ export {
2605
2254
  socketEventNames,
2606
2255
  startSpan,
2607
2256
  startSpanAcrossTrackers,
2257
+ startSpanHandle,
2608
2258
  tryCatch,
2259
+ tryCatchSync,
2609
2260
  unregisterApiAbortController,
2610
2261
  unregisterSyncAbortController,
2611
2262
  validateInputByType,
2612
2263
  validatePagePath,
2613
2264
  validateRequest,
2614
2265
  waitForSocket,
2615
- writeBootUuid
2266
+ writeBootUuid,
2267
+ writeSession
2616
2268
  };
2617
2269
  //# sourceMappingURL=index.js.map