@luckystack/core 0.9.2 → 0.10.1

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
@@ -2,11 +2,13 @@ import {
2
2
  DEFAULT_CSRF_CONFIG,
3
3
  DEFAULT_PAGE_ROUTE_RULES,
4
4
  INVALID_ERROR_RESPONSE_CODE,
5
+ ROUTED_DATA_QUERY_KEY,
5
6
  buildApiResponseEventName,
6
7
  buildApiStreamEventName,
7
8
  buildGetJoinedRoomsResponseEventName,
8
9
  buildJoinRoomResponseEventName,
9
10
  buildLeaveRoomResponseEventName,
11
+ buildRoutedGetUrl,
10
12
  buildSyncProgressEventName,
11
13
  buildSyncResponseEventName,
12
14
  clearCsrfToken,
@@ -49,7 +51,7 @@ import {
49
51
  tryCatchSync,
50
52
  validatePagePath,
51
53
  waitForSocket
52
- } from "./chunk-VRKJHE7E.js";
54
+ } from "./chunk-5FXBTGYL.js";
53
55
  import {
54
56
  getDeployConfig,
55
57
  getPortOverride,
@@ -335,68 +337,6 @@ var applyErrorFormatter = (input) => {
335
337
  return response;
336
338
  };
337
339
 
338
- // src/socketTypes.ts
339
- var _ioInstance = null;
340
- var setIoInstance = (io) => {
341
- _ioInstance = io;
342
- };
343
- var getIoInstance = () => _ioInstance;
344
-
345
- // src/runtimeMapsRegistry.ts
346
- var emptyApi = { apisObject: {}, functionsObject: {} };
347
- var emptySync = { syncObject: {}, functionsObject: {} };
348
- var registry3 = createRegistry({
349
- getRuntimeApiMaps: () => Promise.resolve(emptyApi),
350
- getRuntimeSyncMaps: () => Promise.resolve(emptySync)
351
- });
352
- var registerRuntimeMapsProvider = (provider2) => {
353
- registry3.register(provider2);
354
- };
355
- var isRuntimeMapsProviderRegistered = () => registry3.isRegistered();
356
- var getRuntimeApiMaps = async () => registry3.get().getRuntimeApiMaps();
357
- var getRuntimeSyncMaps = async () => registry3.get().getRuntimeSyncMaps();
358
-
359
- // src/emailRegistry.ts
360
- var activeSender = null;
361
- var registry4 = {};
362
- var registerEmailSender = (sender) => {
363
- activeSender = sender;
364
- registry4 = { ...registry4, default: sender };
365
- };
366
- var registerEmailSenders = (senders) => {
367
- registry4 = { ...senders };
368
- if (senders.default) {
369
- activeSender = senders.default;
370
- }
371
- };
372
- var getEmailSender = () => activeSender ?? registry4.default ?? null;
373
- var getEmailSenderByName = (name) => {
374
- if (registry4[name]) return registry4[name];
375
- if (name === "default" && activeSender) return activeSender;
376
- return null;
377
- };
378
- var listEmailSenderNames = () => Object.keys(registry4);
379
- var isEmailSenderRegistered = () => activeSender !== null || Object.keys(registry4).length > 0;
380
-
381
- // src/localeReloader.ts
382
- var registry5 = createRegistry(null);
383
- var registerLocaleReloader = (reloader) => {
384
- registry5.register(reloader);
385
- return reloader;
386
- };
387
- var getLocaleReloader = () => registry5.get();
388
-
389
- // src/errorTrackerIdentity.ts
390
- import { AsyncLocalStorage } from "async_hooks";
391
- var identityStore = new AsyncLocalStorage();
392
- var runWithErrorTrackerIdentityScope = (fn) => identityStore.run({ user: null }, fn);
393
- var runWithErrorTrackerIdentity = (user, fn) => identityStore.run({ user }, fn);
394
- var setCurrentErrorTrackerIdentity = (user) => {
395
- const box = identityStore.getStore();
396
- if (box) box.user = user;
397
- };
398
- var getCurrentErrorTrackerIdentity = () => identityStore.getStore()?.user ?? null;
399
-
400
340
  // src/env.ts
401
341
  import { existsSync, readFileSync } from "fs";
402
342
  import path from "path";
@@ -510,6 +450,142 @@ var resolveRuntimeMode = () => {
510
450
  var isProductionRuntime = () => resolveRuntimeMode() === "production";
511
451
  var isTestRuntime = () => resolveRuntimeMode() === "test";
512
452
 
453
+ // src/localSocketEnumerationGuard.ts
454
+ var ENUMERATING_MAP_MEMBERS = /* @__PURE__ */ new Set([
455
+ "values",
456
+ "keys",
457
+ "entries",
458
+ "forEach",
459
+ "size",
460
+ Symbol.iterator
461
+ ]);
462
+ var PER_INSTANCE_ADAPTER_MEMBERS = /* @__PURE__ */ new Set(["rooms", "sids"]);
463
+ var LocalSocketEnumerationError = class extends Error {
464
+ constructor(surface) {
465
+ super(
466
+ `${surface} only sees the sockets connected to THIS instance. With the Redis adapter every other instance is invisible here, so a fan-out or membership check built on it silently misses them. Use \`getRoomSockets(room, { userId })\` from \`@luckystack/core\` or \`io.in(room).fetchSockets()\` for a cross-instance view, or \`getIoInstance({ raw: true })\` when you deliberately want the local one. (Thrown outside production only.)`
467
+ );
468
+ this.name = "LocalSocketEnumerationError";
469
+ }
470
+ };
471
+ var readBound = (target, prop) => {
472
+ const value = Reflect.get(target, prop);
473
+ return typeof value === "function" ? value.bind(target) : value;
474
+ };
475
+ var guardSocketsMap = (map) => new Proxy(map, {
476
+ get(target, prop) {
477
+ if (ENUMERATING_MAP_MEMBERS.has(prop)) {
478
+ throw new LocalSocketEnumerationError("Enumerating `io.sockets.sockets`");
479
+ }
480
+ return readBound(target, prop);
481
+ }
482
+ });
483
+ var guardAdapter = (adapter) => new Proxy(adapter, {
484
+ get(target, prop) {
485
+ if (PER_INSTANCE_ADAPTER_MEMBERS.has(prop)) {
486
+ throw new LocalSocketEnumerationError(`\`io.sockets.adapter.${String(prop)}\``);
487
+ }
488
+ return readBound(target, prop);
489
+ }
490
+ });
491
+ var guardNamespace = (namespace) => new Proxy(namespace, {
492
+ get(target, prop) {
493
+ if (prop === "adapter") return guardAdapter(target.adapter);
494
+ if (prop === "sockets") return guardSocketsMap(target.sockets);
495
+ return readBound(target, prop);
496
+ }
497
+ });
498
+ var guardLocalSocketEnumeration = (io) => new Proxy(io, {
499
+ get(target, prop) {
500
+ if (prop === "sockets") return guardNamespace(target.sockets);
501
+ return readBound(target, prop);
502
+ }
503
+ });
504
+
505
+ // src/socketTypes.ts
506
+ var _ioInstance = null;
507
+ var _guardedIoInstance = null;
508
+ var setIoInstance = (io) => {
509
+ _ioInstance = io;
510
+ _guardedIoInstance = null;
511
+ };
512
+ var getIoInstance = (options) => {
513
+ if (_ioInstance === null) return null;
514
+ if (options?.raw === true || isProductionRuntime()) return _ioInstance;
515
+ _guardedIoInstance ??= guardLocalSocketEnumeration(_ioInstance);
516
+ return _guardedIoInstance;
517
+ };
518
+
519
+ // src/roomSockets.ts
520
+ var getRoomSockets = async (room, options = {}) => {
521
+ const io = getIoInstance({ raw: true });
522
+ if (io === null) {
523
+ throw new Error("getRoomSockets: no Socket.io server is registered (setIoInstance was never called), so the sockets of a room cannot be resolved.");
524
+ }
525
+ const logicalRoom = room.trim();
526
+ if (logicalRoom === "") {
527
+ throw new Error("getRoomSockets: `room` must be a non-empty room code.");
528
+ }
529
+ if (logicalRoom === "all") return io.fetchSockets();
530
+ const physicalRoom = formatRoomName(logicalRoom, { purpose: "broadcast", userId: options.userId ?? null });
531
+ return io.in(physicalRoom).fetchSockets();
532
+ };
533
+
534
+ // src/runtimeMapsRegistry.ts
535
+ var emptyApi = { apisObject: {}, functionsObject: {} };
536
+ var emptySync = { syncObject: {}, functionsObject: {} };
537
+ var registry3 = createRegistry({
538
+ getRuntimeApiMaps: () => Promise.resolve(emptyApi),
539
+ getRuntimeSyncMaps: () => Promise.resolve(emptySync)
540
+ });
541
+ var registerRuntimeMapsProvider = (provider2) => {
542
+ registry3.register(provider2);
543
+ };
544
+ var isRuntimeMapsProviderRegistered = () => registry3.isRegistered();
545
+ var getRuntimeApiMaps = async () => registry3.get().getRuntimeApiMaps();
546
+ var getRuntimeSyncMaps = async () => registry3.get().getRuntimeSyncMaps();
547
+
548
+ // src/emailRegistry.ts
549
+ var activeSender = null;
550
+ var registry4 = {};
551
+ var registerEmailSender = (sender) => {
552
+ activeSender = sender;
553
+ registry4 = { ...registry4, default: sender };
554
+ };
555
+ var registerEmailSenders = (senders) => {
556
+ registry4 = { ...senders };
557
+ if (senders.default) {
558
+ activeSender = senders.default;
559
+ }
560
+ };
561
+ var getEmailSender = () => activeSender ?? registry4.default ?? null;
562
+ var getEmailSenderByName = (name) => {
563
+ if (registry4[name]) return registry4[name];
564
+ if (name === "default" && activeSender) return activeSender;
565
+ return null;
566
+ };
567
+ var listEmailSenderNames = () => Object.keys(registry4);
568
+ var isEmailSenderRegistered = () => activeSender !== null || Object.keys(registry4).length > 0;
569
+
570
+ // src/localeReloader.ts
571
+ var registry5 = createRegistry(null);
572
+ var registerLocaleReloader = (reloader) => {
573
+ registry5.register(reloader);
574
+ return reloader;
575
+ };
576
+ var getLocaleReloader = () => registry5.get();
577
+
578
+ // src/errorTrackerIdentity.ts
579
+ import { AsyncLocalStorage } from "async_hooks";
580
+ var identityStore = new AsyncLocalStorage();
581
+ var runWithErrorTrackerIdentityScope = (fn) => identityStore.run({ user: null }, fn);
582
+ var runWithErrorTrackerIdentity = (user, fn) => identityStore.run({ user }, fn);
583
+ var setCurrentErrorTrackerIdentity = (user) => {
584
+ const box = identityStore.getStore();
585
+ if (box) box.user = user;
586
+ };
587
+ var getCurrentErrorTrackerIdentity = () => identityStore.getStore()?.user ?? null;
588
+
513
589
  // src/db.ts
514
590
  import { createRequire as createRequire2 } from "module";
515
591
 
@@ -865,17 +941,6 @@ var releaseLease = async (name, token) => {
865
941
 
866
942
  // src/socketRedisAdapter.ts
867
943
  import { createAdapter } from "@socket.io/redis-adapter";
868
- var attachSocketRedisAdapter = (io, options = {}) => {
869
- const pubClient = options.pubClient ?? redis.duplicate();
870
- const subClient = options.subClient ?? redis.duplicate();
871
- pubClient.on("error", (err) => {
872
- getLogger().error("[socket-redis-adapter] pub client error", err);
873
- });
874
- subClient.on("error", (err) => {
875
- getLogger().error("[socket-redis-adapter] sub client error", err);
876
- });
877
- io.adapter(createAdapter(pubClient, subClient, options.adapterOptions));
878
- };
879
944
 
880
945
  // src/bootUuid.ts
881
946
  import { randomUUID } from "crypto";
@@ -936,6 +1001,23 @@ var startBootUuidHeartbeat = (envKey) => {
936
1001
  };
937
1002
  };
938
1003
 
1004
+ // src/socketRedisAdapter.ts
1005
+ var resolveSocketAdapterKey = () => `${getProjectName()}:${resolveEnvKey()}:socket.io`;
1006
+ var attachSocketRedisAdapter = (io, options = {}) => {
1007
+ const pubClient = options.pubClient ?? redis.duplicate();
1008
+ const subClient = options.subClient ?? redis.duplicate();
1009
+ pubClient.on("error", (err) => {
1010
+ getLogger().error("[socket-redis-adapter] pub client error", err);
1011
+ });
1012
+ subClient.on("error", (err) => {
1013
+ getLogger().error("[socket-redis-adapter] sub client error", err);
1014
+ });
1015
+ io.adapter(createAdapter(pubClient, subClient, {
1016
+ ...options.adapterOptions,
1017
+ key: options.adapterOptions?.key ?? resolveSocketAdapterKey()
1018
+ }));
1019
+ };
1020
+
939
1021
  // src/synchronizedEnvHashes.ts
940
1022
  import { createHash, createHmac } from "crypto";
941
1023
  var plainHash = (value) => createHash("sha256").update(value).digest("hex");
@@ -2257,9 +2339,11 @@ export {
2257
2339
  GENERATED_API_SCHEMAS_PATH,
2258
2340
  GENERATED_SOCKET_TYPES_PATH,
2259
2341
  INVALID_ERROR_RESPONSE_CODE,
2342
+ LocalSocketEnumerationError,
2260
2343
  PUBLIC_DIR,
2261
2344
  REDACTED_PLACEHOLDER,
2262
2345
  ROOT_DIR,
2346
+ ROUTED_DATA_QUERY_KEY,
2263
2347
  SERVER_DIR,
2264
2348
  SERVER_FUNCTIONS_DIR,
2265
2349
  SHARED_DIR,
@@ -2286,6 +2370,7 @@ export {
2286
2370
  buildGetJoinedRoomsResponseEventName,
2287
2371
  buildJoinRoomResponseEventName,
2288
2372
  buildLeaveRoomResponseEventName,
2373
+ buildRoutedGetUrl,
2289
2374
  buildSyncProgressEventName,
2290
2375
  buildSyncResponseEventName,
2291
2376
  captureException,
@@ -2370,6 +2455,7 @@ export {
2370
2455
  getRegisteredApiMethod,
2371
2456
  getRegisteredSessionProvider,
2372
2457
  getRoomNameFormatter,
2458
+ getRoomSockets,
2373
2459
  getRuntimeApiMaps,
2374
2460
  getRuntimeSyncMaps,
2375
2461
  getServerDir,
@@ -2494,6 +2580,7 @@ export {
2494
2580
  resolveHealthHashConfig,
2495
2581
  resolveHealthHashConfigFromDescriptor,
2496
2582
  resolveRuntimeMode,
2583
+ resolveSocketAdapterKey,
2497
2584
  runWithErrorTrackerIdentity,
2498
2585
  runWithErrorTrackerIdentityScope,
2499
2586
  sanitizeErrorString,