@luckystack/core 0.9.2 → 0.10.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
@@ -335,68 +335,6 @@ var applyErrorFormatter = (input) => {
335
335
  return response;
336
336
  };
337
337
 
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
338
  // src/env.ts
401
339
  import { existsSync, readFileSync } from "fs";
402
340
  import path from "path";
@@ -510,6 +448,142 @@ var resolveRuntimeMode = () => {
510
448
  var isProductionRuntime = () => resolveRuntimeMode() === "production";
511
449
  var isTestRuntime = () => resolveRuntimeMode() === "test";
512
450
 
451
+ // src/localSocketEnumerationGuard.ts
452
+ var ENUMERATING_MAP_MEMBERS = /* @__PURE__ */ new Set([
453
+ "values",
454
+ "keys",
455
+ "entries",
456
+ "forEach",
457
+ "size",
458
+ Symbol.iterator
459
+ ]);
460
+ var PER_INSTANCE_ADAPTER_MEMBERS = /* @__PURE__ */ new Set(["rooms", "sids"]);
461
+ var LocalSocketEnumerationError = class extends Error {
462
+ constructor(surface) {
463
+ super(
464
+ `${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.)`
465
+ );
466
+ this.name = "LocalSocketEnumerationError";
467
+ }
468
+ };
469
+ var readBound = (target, prop) => {
470
+ const value = Reflect.get(target, prop);
471
+ return typeof value === "function" ? value.bind(target) : value;
472
+ };
473
+ var guardSocketsMap = (map) => new Proxy(map, {
474
+ get(target, prop) {
475
+ if (ENUMERATING_MAP_MEMBERS.has(prop)) {
476
+ throw new LocalSocketEnumerationError("Enumerating `io.sockets.sockets`");
477
+ }
478
+ return readBound(target, prop);
479
+ }
480
+ });
481
+ var guardAdapter = (adapter) => new Proxy(adapter, {
482
+ get(target, prop) {
483
+ if (PER_INSTANCE_ADAPTER_MEMBERS.has(prop)) {
484
+ throw new LocalSocketEnumerationError(`\`io.sockets.adapter.${String(prop)}\``);
485
+ }
486
+ return readBound(target, prop);
487
+ }
488
+ });
489
+ var guardNamespace = (namespace) => new Proxy(namespace, {
490
+ get(target, prop) {
491
+ if (prop === "adapter") return guardAdapter(target.adapter);
492
+ if (prop === "sockets") return guardSocketsMap(target.sockets);
493
+ return readBound(target, prop);
494
+ }
495
+ });
496
+ var guardLocalSocketEnumeration = (io) => new Proxy(io, {
497
+ get(target, prop) {
498
+ if (prop === "sockets") return guardNamespace(target.sockets);
499
+ return readBound(target, prop);
500
+ }
501
+ });
502
+
503
+ // src/socketTypes.ts
504
+ var _ioInstance = null;
505
+ var _guardedIoInstance = null;
506
+ var setIoInstance = (io) => {
507
+ _ioInstance = io;
508
+ _guardedIoInstance = null;
509
+ };
510
+ var getIoInstance = (options) => {
511
+ if (_ioInstance === null) return null;
512
+ if (options?.raw === true || isProductionRuntime()) return _ioInstance;
513
+ _guardedIoInstance ??= guardLocalSocketEnumeration(_ioInstance);
514
+ return _guardedIoInstance;
515
+ };
516
+
517
+ // src/roomSockets.ts
518
+ var getRoomSockets = async (room, options = {}) => {
519
+ const io = getIoInstance({ raw: true });
520
+ if (io === null) {
521
+ throw new Error("getRoomSockets: no Socket.io server is registered (setIoInstance was never called), so the sockets of a room cannot be resolved.");
522
+ }
523
+ const logicalRoom = room.trim();
524
+ if (logicalRoom === "") {
525
+ throw new Error("getRoomSockets: `room` must be a non-empty room code.");
526
+ }
527
+ if (logicalRoom === "all") return io.fetchSockets();
528
+ const physicalRoom = formatRoomName(logicalRoom, { purpose: "broadcast", userId: options.userId ?? null });
529
+ return io.in(physicalRoom).fetchSockets();
530
+ };
531
+
532
+ // src/runtimeMapsRegistry.ts
533
+ var emptyApi = { apisObject: {}, functionsObject: {} };
534
+ var emptySync = { syncObject: {}, functionsObject: {} };
535
+ var registry3 = createRegistry({
536
+ getRuntimeApiMaps: () => Promise.resolve(emptyApi),
537
+ getRuntimeSyncMaps: () => Promise.resolve(emptySync)
538
+ });
539
+ var registerRuntimeMapsProvider = (provider2) => {
540
+ registry3.register(provider2);
541
+ };
542
+ var isRuntimeMapsProviderRegistered = () => registry3.isRegistered();
543
+ var getRuntimeApiMaps = async () => registry3.get().getRuntimeApiMaps();
544
+ var getRuntimeSyncMaps = async () => registry3.get().getRuntimeSyncMaps();
545
+
546
+ // src/emailRegistry.ts
547
+ var activeSender = null;
548
+ var registry4 = {};
549
+ var registerEmailSender = (sender) => {
550
+ activeSender = sender;
551
+ registry4 = { ...registry4, default: sender };
552
+ };
553
+ var registerEmailSenders = (senders) => {
554
+ registry4 = { ...senders };
555
+ if (senders.default) {
556
+ activeSender = senders.default;
557
+ }
558
+ };
559
+ var getEmailSender = () => activeSender ?? registry4.default ?? null;
560
+ var getEmailSenderByName = (name) => {
561
+ if (registry4[name]) return registry4[name];
562
+ if (name === "default" && activeSender) return activeSender;
563
+ return null;
564
+ };
565
+ var listEmailSenderNames = () => Object.keys(registry4);
566
+ var isEmailSenderRegistered = () => activeSender !== null || Object.keys(registry4).length > 0;
567
+
568
+ // src/localeReloader.ts
569
+ var registry5 = createRegistry(null);
570
+ var registerLocaleReloader = (reloader) => {
571
+ registry5.register(reloader);
572
+ return reloader;
573
+ };
574
+ var getLocaleReloader = () => registry5.get();
575
+
576
+ // src/errorTrackerIdentity.ts
577
+ import { AsyncLocalStorage } from "async_hooks";
578
+ var identityStore = new AsyncLocalStorage();
579
+ var runWithErrorTrackerIdentityScope = (fn) => identityStore.run({ user: null }, fn);
580
+ var runWithErrorTrackerIdentity = (user, fn) => identityStore.run({ user }, fn);
581
+ var setCurrentErrorTrackerIdentity = (user) => {
582
+ const box = identityStore.getStore();
583
+ if (box) box.user = user;
584
+ };
585
+ var getCurrentErrorTrackerIdentity = () => identityStore.getStore()?.user ?? null;
586
+
513
587
  // src/db.ts
514
588
  import { createRequire as createRequire2 } from "module";
515
589
 
@@ -865,17 +939,6 @@ var releaseLease = async (name, token) => {
865
939
 
866
940
  // src/socketRedisAdapter.ts
867
941
  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
942
 
880
943
  // src/bootUuid.ts
881
944
  import { randomUUID } from "crypto";
@@ -936,6 +999,23 @@ var startBootUuidHeartbeat = (envKey) => {
936
999
  };
937
1000
  };
938
1001
 
1002
+ // src/socketRedisAdapter.ts
1003
+ var resolveSocketAdapterKey = () => `${getProjectName()}:${resolveEnvKey()}:socket.io`;
1004
+ var attachSocketRedisAdapter = (io, options = {}) => {
1005
+ const pubClient = options.pubClient ?? redis.duplicate();
1006
+ const subClient = options.subClient ?? redis.duplicate();
1007
+ pubClient.on("error", (err) => {
1008
+ getLogger().error("[socket-redis-adapter] pub client error", err);
1009
+ });
1010
+ subClient.on("error", (err) => {
1011
+ getLogger().error("[socket-redis-adapter] sub client error", err);
1012
+ });
1013
+ io.adapter(createAdapter(pubClient, subClient, {
1014
+ ...options.adapterOptions,
1015
+ key: options.adapterOptions?.key ?? resolveSocketAdapterKey()
1016
+ }));
1017
+ };
1018
+
939
1019
  // src/synchronizedEnvHashes.ts
940
1020
  import { createHash, createHmac } from "crypto";
941
1021
  var plainHash = (value) => createHash("sha256").update(value).digest("hex");
@@ -2257,6 +2337,7 @@ export {
2257
2337
  GENERATED_API_SCHEMAS_PATH,
2258
2338
  GENERATED_SOCKET_TYPES_PATH,
2259
2339
  INVALID_ERROR_RESPONSE_CODE,
2340
+ LocalSocketEnumerationError,
2260
2341
  PUBLIC_DIR,
2261
2342
  REDACTED_PLACEHOLDER,
2262
2343
  ROOT_DIR,
@@ -2370,6 +2451,7 @@ export {
2370
2451
  getRegisteredApiMethod,
2371
2452
  getRegisteredSessionProvider,
2372
2453
  getRoomNameFormatter,
2454
+ getRoomSockets,
2373
2455
  getRuntimeApiMaps,
2374
2456
  getRuntimeSyncMaps,
2375
2457
  getServerDir,
@@ -2494,6 +2576,7 @@ export {
2494
2576
  resolveHealthHashConfig,
2495
2577
  resolveHealthHashConfigFromDescriptor,
2496
2578
  resolveRuntimeMode,
2579
+ resolveSocketAdapterKey,
2497
2580
  runWithErrorTrackerIdentity,
2498
2581
  runWithErrorTrackerIdentityScope,
2499
2582
  sanitizeErrorString,