@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/CHANGELOG.md +18 -1
- package/CLAUDE.md +6 -4
- package/dist/client.js.map +1 -1
- package/dist/eslint/index.js +63 -14
- package/dist/eslint/index.js.map +1 -1
- package/dist/index.d.ts +68 -6
- package/dist/index.js +156 -73
- package/dist/index.js.map +1 -1
- package/docs/redis-adapter.md +4 -1
- package/docs/socket-bootstrap.md +25 -2
- package/package.json +1 -1
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,
|