@luckystack/sync 0.2.7 → 0.3.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/client.js CHANGED
@@ -281,6 +281,7 @@ var syncRequestInternal = (params) => {
281
281
  if (typeof effectiveTimeoutMs === "number" && effectiveTimeoutMs > 0) {
282
282
  responseTimeout = setTimeout(() => {
283
283
  responseTimeout = null;
284
+ socketInstance.emit(socketEventNames.syncCancel, { cb: serverCancelId ?? syncCb });
284
285
  cleanupProgressListener?.();
285
286
  cleanupProgressListener = null;
286
287
  cleanupResponseListener?.();
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/syncRequest.ts"],"sourcesContent":["//? All cross-package imports go through `@luckystack/core/client` (the\n//? browser-safe subpath) — never the main `@luckystack/core` barrel which\n//? re-exports server-only modules (paths.ts, db.ts, redis.ts) that must\n//? not enter a Vite client bundle.\nimport type {\n BaseSessionLayout as SessionLayout,\n StreamPayload,\n SyncTypeMap,\n statusContent,\n} from \"@luckystack/core/client\";\nimport {\n getLogger,\n getProjectConfig,\n notify,\n incrementResponseIndex,\n socket,\n waitForSocket,\n enqueueSyncRequest,\n isOnline,\n normalizeErrorResponseCore,\n parseServiceRouteName,\n buildSyncProgressEventName,\n buildSyncResponseEventName,\n socketEventNames,\n} from \"@luckystack/core/client\";\nimport { Dispatch, RefObject, SetStateAction, useCallback, useEffect, useRef } from \"react\";\nimport { Socket } from \"socket.io-client\";\n\nexport type SyncRequestStreamEvent<T extends StreamPayload = StreamPayload> = T;\n\nexport type SyncRouteStreamEvent<T extends StreamPayload = StreamPayload> = T;\n\ntype SyncRequestStreamCallback = (event: SyncRequestStreamEvent) => void;\ntype SyncEventStreamCallback = (params: { stream: SyncRouteStreamEvent }) => void;\n\nconst shouldLogDev = () => getProjectConfig().logging.devLogs;\nconst shouldNotifyDev = () => getProjectConfig().logging.devNotifications;\nconst shouldLogSocketStatus = () => getProjectConfig().logging.socketStatus;\nconst shouldLogStream = () => getProjectConfig().logging.stream;\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// Type Helpers for Sync Requests\n// ═══════════════════════════════════════════════════════════════════════════════\n\n// Check if data input is required (i.e., T does NOT allow empty object)\n// Unions like {a:1} | {b:1} do NOT allow {}, so data will be required\ntype DataRequired<T> = Record<string, never> extends T ? false : true;\n\ntype UnionToIntersection<U> =\n (U extends unknown ? (arg: U) => void : never) extends ((arg: infer I) => void)\n ? I\n : never;\n\ntype Prettify<T> = { [K in keyof T]: T[K] } & {};\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// Global Sync Params\n// ═══════════════════════════════════════════════════════════════════════════════\n\n// All possible sync names across all pages\ntype SyncRouteRecord = UnionToIntersection<{\n [P in keyof SyncTypeMap]: {\n [N in keyof SyncTypeMap[P] as P extends 'root'\n ? `system/${Extract<N, string>}`\n : `${Extract<P, string>}/${Extract<N, string>}`]: SyncTypeMap[P][N]\n }\n}[keyof SyncTypeMap]>;\n\ntype SyncFullName = Extract<keyof SyncRouteRecord, string>;\ntype VersionsForFullName<F extends SyncFullName> = Extract<keyof SyncRouteRecord[F], string>;\n\ntype ClientInputForFullName<F extends SyncFullName, V extends VersionsForFullName<F>> = SyncRouteRecord[F][V] extends { clientInput: infer I }\n ? I\n : never;\n\ntype ServerOutputForFullName<F extends SyncFullName, V extends VersionsForFullName<F>> = SyncRouteRecord[F][V] extends { serverOutput: infer O }\n ? O\n : never;\n\ntype ClientOutputForFullName<F extends SyncFullName, V extends VersionsForFullName<F>> = SyncRouteRecord[F][V] extends { clientOutput: infer O }\n ? O\n : never;\n\ntype ServerStreamForFullName<F extends SyncFullName, V extends VersionsForFullName<F>> = SyncRouteRecord[F][V] extends { serverStream: infer O }\n ? O\n : never;\n\ntype ClientStreamForFullName<F extends SyncFullName, V extends VersionsForFullName<F>> = SyncRouteRecord[F][V] extends { clientStream: infer O }\n ? O\n : never;\n\ntype SyncRequestStreamCallbackForFullName<F extends SyncFullName, V extends VersionsForFullName<F>> =\n [ServerStreamForFullName<F, V>] extends [never]\n ? never\n : (event: SyncRequestStreamEvent<Prettify<ServerStreamForFullName<F, V> extends StreamPayload ? ServerStreamForFullName<F, V> : StreamPayload>>) => void;\n\n//? Recipients see chunks from THREE sources, all on the same wire channel:\n//? 1. `_client_v{n}.ts`'s `stream(...)` (per-recipient, runs after _server)\n//? 2. `_server_v{n}.ts`'s `broadcastStream(...)` (room-wide fan-out)\n//? 3. `_server_v{n}.ts`'s `streamTo(tokens, ...)` (selective fan-out)\n//? All three flow into `upsertSyncEventCallback`'s `stream` argument. Both\n//? serverStream and clientStream are folded into the union so the callback\n//? sees every shape the route can emit. If neither side ever streams, the\n//? callback type collapses to never (compile error to register one).\ntype CombinedRouteStream<F extends SyncFullName, V extends VersionsForFullName<F>> =\n | (ClientStreamForFullName<F, V> extends never ? never : ClientStreamForFullName<F, V>)\n | (ServerStreamForFullName<F, V> extends never ? never : ServerStreamForFullName<F, V>);\n\ntype SyncRouteStreamCallbackForFullName<F extends SyncFullName, V extends VersionsForFullName<F>> =\n [CombinedRouteStream<F, V>] extends [never]\n ? never\n : (params: { stream: SyncRouteStreamEvent<Prettify<CombinedRouteStream<F, V> extends StreamPayload ? CombinedRouteStream<F, V> : StreamPayload>> }) => void;\n\ntype SyncParamsForFullName<\n F extends SyncFullName,\n V extends VersionsForFullName<F>\n> = DataRequired<ClientInputForFullName<F, V>> extends true\n ? {\n name: F;\n version: V;\n data: ClientInputForFullName<F, V>;\n receiver: string;\n ignoreSelf?: boolean;\n onStream?: SyncRequestStreamCallbackForFullName<F, V>;\n /**\n * Per-request override of `projectConfig.offlineQueue.dropPolicy`. Lets a\n * specific sync (\"editor cursor move\") pick `'drop-oldest'` while the\n * app default stays `'reject'` for safer sends. When omitted, falls back\n * to the global config.\n */\n offlineDropPolicy?: 'drop-oldest' | 'drop-newest' | 'reject';\n /**\n * Optional AbortSignal. When aborted the client emits `syncCancel { cb }`\n * to the server and resolves locally with\n * `{ status: 'error', errorCode: 'request.aborted' }`.\n */\n signal?: AbortSignal;\n }\n : {\n name: F;\n version: V;\n data?: ClientInputForFullName<F, V>;\n receiver: string;\n ignoreSelf?: boolean;\n onStream?: SyncRequestStreamCallbackForFullName<F, V>;\n /** Per-request override (see typed branch). */\n offlineDropPolicy?: 'drop-oldest' | 'drop-newest' | 'reject';\n /** Optional AbortSignal — see typed branch. */\n signal?: AbortSignal;\n };\n\ninterface RuntimeSyncParams {\n name?: string;\n version?: string;\n data?: unknown;\n receiver?: string;\n ignoreSelf?: boolean;\n onStream?: SyncRequestStreamCallback;\n offlineDropPolicy?: 'drop-oldest' | 'drop-newest' | 'reject';\n signal?: AbortSignal;\n}\n\ninterface SyncErrorParam { key: string; value: string | number | boolean };\n\ninterface SyncResponseError {\n status: 'error';\n message: string;\n errorCode: string;\n errorParams?: SyncErrorParam[];\n httpStatus?: number;\n}\n\ninterface SyncAckResponse {\n status?: 'success' | 'error';\n message?: string;\n result?: unknown;\n errorCode?: string;\n errorParams?: SyncErrorParam[];\n httpStatus?: number;\n}\n\ntype SyncResultForFullName<F extends SyncFullName, V extends VersionsForFullName<F>> =\n [ServerOutputForFullName<F, V>] extends [never]\n ? Record<string, never>\n : ServerOutputForFullName<F, V>;\n\ntype SyncRequestResponseForFullName<F extends SyncFullName, V extends VersionsForFullName<F>> =\n | SyncResponseError\n | {\n status: 'success';\n message: string;\n result: SyncResultForFullName<F, V>;\n };\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// Sync Event Callbacks Registry\n// ═══════════════════════════════════════════════════════════════════════════════\n\ntype SyncEventCallback = (params: { clientOutput: unknown; serverOutput: unknown }) => void;\nconst syncEvents: Partial<Record<string, SyncEventCallback[]>> = {};\nconst syncStreamEvents: Partial<Record<string, SyncEventStreamCallback[]>> = {};\nconst noop = () => null;\n\ninterface SyncLifecycleHandlers {\n connect: () => void;\n disconnect: () => void;\n reconnectAttempt: (attempt: number) => void;\n userAfk: (payload: { userId: string; endTime?: number }) => void;\n userBack: (payload: { userId: string }) => void;\n connectError: (err: { message: string }) => void;\n}\n\nlet activeLifecycleHandlers: SyncLifecycleHandlers | null = null;\n\nconst canSendNow = (socketInstance: Socket) => {\n if (!socketInstance.connected) return false;\n return isOnline();\n};\n\nconst createQueueId = () => {\n return `${String(Date.now())}-${String(Math.random())}`;\n};\n\nconst getCallbacksForRoute = (route: string): SyncEventCallback[] => {\n syncEvents[route] ??= [];\n return syncEvents[route];\n};\n\nconst getStreamCallbacksForRoute = (route: string): SyncEventStreamCallback[] => {\n syncStreamEvents[route] ??= [];\n return syncStreamEvents[route];\n};\n\nconst triggerSyncCallbacks = (name: string, clientOutput: unknown, serverOutput: unknown) => {\n const callbacks = syncEvents[name] ?? [];\n if (callbacks.length === 0) {\n //? Streaming-only routes legitimately register ONLY a stream callback —\n //? the final success envelope is implicit in the stream completing. Only\n //? warn when NEITHER callback type is registered for the route.\n const streamCallbacks = syncStreamEvents[name] ?? [];\n if (streamCallbacks.length === 0 && shouldLogDev()) {\n getLogger().warn(`Sync event ${name} has no registered callback on this page`);\n }\n return;\n }\n\n for (const callback of callbacks) {\n callback({ clientOutput, serverOutput });\n }\n};\n\nconst triggerSyncStreamCallbacks = (name: string, stream: SyncRouteStreamEvent) => {\n const callbacks = syncStreamEvents[name] ?? [];\n if (callbacks.length === 0) {\n return;\n }\n\n for (const callback of callbacks) {\n callback({ stream });\n }\n};\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// syncRequest Function Overloads\n// ═══════════════════════════════════════════════════════════════════════════════\n\nconst normalizeSyncError = ({\n response,\n fallbackErrorCode,\n}: {\n response: SyncAckResponse;\n fallbackErrorCode: string;\n}): SyncResponseError => {\n const normalized = normalizeErrorResponseCore({\n response,\n fallbackErrorCode,\n resolveMessage: ({ errorCode }) => {\n if (typeof response.message === 'string' && response.message.trim().length > 0) {\n return response.message;\n }\n\n return errorCode;\n },\n });\n\n return {\n status: 'error',\n message: normalized.message,\n errorCode: normalized.errorCode,\n errorParams: normalized.errorParams,\n httpStatus: normalized.httpStatus,\n };\n};\n\ntype SyncRequestParamsWithOptions<F extends SyncFullName, V extends VersionsForFullName<F>> =\n SyncParamsForFullName<F, V>;\n\n//? Outcome of the pre-flight validation phase of a sync send. Either a\n//? normalized error envelope to resolve the request with, or the sanitized\n//? values the send phase needs. Extracted from `syncRequestInternal` so the\n//? \"validate first, execute second\" split is explicit (the orchestrator stays\n//? a thin promise wrapper around prepare → run).\ntype PreparedSyncRequest =\n | { ok: false; error: SyncResponseError }\n | { ok: true; sanitizedName: string; version: string; data: object; normalizedReceiver: string };\n\n//? Pure-ish validation chain (logs + dev-notifies on failure, same as the\n//? previous inlined block). Async because the socket-readiness gate\n//? (`waitForSocket`) is awaited last, after the cheap structural checks. Each\n//? failure path returns the SAME normalized error envelope the inlined code\n//? resolved with — behaviour-identical.\nconst prepareSyncRequest = async ({\n name,\n version,\n receiver,\n payloadData,\n}: {\n name: unknown;\n version: unknown;\n receiver: unknown;\n payloadData: unknown;\n}): Promise<PreparedSyncRequest> => {\n if (!name || typeof name !== \"string\") {\n if (shouldLogDev()) {\n getLogger().error(\"Invalid name for syncRequest\");\n }\n if (shouldNotifyDev()) {\n notify.error({ key: 'sync.invalidName' });\n }\n return { ok: false, error: normalizeSyncError({\n response: { status: 'error', errorCode: 'sync.invalidName' },\n fallbackErrorCode: 'sync.invalidName',\n }) };\n }\n\n const parsedRoute = parseServiceRouteName(name);\n if (parsedRoute.status === 'error') {\n if (shouldLogDev()) {\n getLogger().error(`[syncRequest] Invalid service route name '${name}'`, undefined, { reason: parsedRoute.reason });\n }\n if (shouldNotifyDev()) {\n notify.error({ key: 'routing.invalidServiceRouteName' });\n }\n return { ok: false, error: normalizeSyncError({\n response: {\n status: 'error',\n errorCode: 'routing.invalidServiceRouteName',\n errorParams: [{ key: 'name', value: name }],\n },\n fallbackErrorCode: 'routing.invalidServiceRouteName',\n }) };\n }\n\n const sanitizedName = parsedRoute.normalizedRouteName;\n\n const data = payloadData && typeof payloadData === \"object\" ? payloadData : {};\n\n if (!version || typeof version !== 'string') {\n if (shouldLogDev()) {\n getLogger().error(\"Invalid version for syncRequest\");\n }\n if (shouldNotifyDev()) {\n notify.error({ key: 'sync.invalidVersion' });\n }\n return { ok: false, error: normalizeSyncError({\n response: { status: 'error', errorCode: 'sync.invalidVersion' },\n fallbackErrorCode: 'sync.invalidVersion',\n }) };\n }\n\n const normalizedReceiver = typeof receiver === 'string' ? receiver.trim() : '';\n\n if (!normalizedReceiver) {\n if (shouldLogDev()) {\n getLogger().error(\"You need to provide a receiver for syncRequest, this can be either 'all' to trigger all sockets which we do not recommend or it can be any value such as a code e.g 'Ag2cg4'. this works together with the joinRoom and leaveRoom function\");\n }\n if (shouldNotifyDev()) {\n notify.error({ key: 'sync.missingReceiver' });\n }\n return { ok: false, error: normalizeSyncError({\n response: { status: 'error', errorCode: 'sync.missingReceiver' },\n fallbackErrorCode: 'sync.missingReceiver',\n }) };\n }\n\n if (!await waitForSocket()) {\n return { ok: false, error: normalizeSyncError({\n response: { status: 'error', errorCode: 'sync.ioUnavailable' },\n fallbackErrorCode: 'sync.ioUnavailable',\n }) };\n }\n if (!socket) {\n return { ok: false, error: normalizeSyncError({\n response: { status: 'error', errorCode: 'sync.ioUnavailable' },\n fallbackErrorCode: 'sync.ioUnavailable',\n }) };\n }\n\n return { ok: true, sanitizedName, version, data, normalizedReceiver };\n};\n\nconst syncRequestInternal = <F extends SyncFullName, V extends VersionsForFullName<F>>(\n params: SyncRequestParamsWithOptions<F, V>\n): Promise<Prettify<SyncRequestResponseForFullName<F, V>>> => {\n const runtimeParams = params as RuntimeSyncParams;\n const { name, version, receiver, ignoreSelf, onStream, offlineDropPolicy, signal: externalSignal } = runtimeParams;\n const payloadData = runtimeParams.data;\n\n type RequestOutput = Prettify<SyncRequestResponseForFullName<F, V>>;\n\n return new Promise<RequestOutput>((resolve) => {\n void (async () => {\n //? B1 — if the consumer-supplied signal is already aborted at call\n //? time, short-circuit before we even touch the socket.\n if (externalSignal?.aborted) {\n resolve(normalizeSyncError({\n response: { status: 'error', errorCode: 'request.aborted' },\n fallbackErrorCode: 'request.aborted',\n }));\n return;\n }\n\n const prepared = await prepareSyncRequest({ name, version, receiver, payloadData });\n if (!prepared.ok) {\n resolve(prepared.error);\n return;\n }\n const { sanitizedName, version: resolvedVersion, data, normalizedReceiver } = prepared;\n\n //? Re-narrow `socket` for the closures below. `prepareSyncRequest`\n //? already guaranteed it is non-null (returns an `ioUnavailable` error\n //? otherwise), but that guard lives in another function so TS can't\n //? carry the narrowing across the call boundary.\n const activeSocket = socket;\n if (!activeSocket) {\n resolve(normalizeSyncError({\n response: { status: 'error', errorCode: 'sync.ioUnavailable' },\n fallbackErrorCode: 'sync.ioUnavailable',\n }));\n return;\n }\n\n const fullName = `sync/${sanitizedName}/${resolvedVersion}`;\n let queueId: string | null = null;\n\n const runRequest = (socketInstance: Socket) => {\n if (!canSendNow(socketInstance)) {\n queueId ??= createQueueId();\n //? For the immediate reject-new path (`drop-newest` / `reject`\n //? policy) THIS item's `onDrop` fires SYNCHRONOUSLY inside\n //? `enqueueSyncRequest`. We keep surfacing that as the historical\n //? `offline.queueFull` via the `!enqueued` branch, so suppress the\n //? `onDrop` resolve during the synchronous enqueue window and re-arm\n //? it only if the item was actually queued (so a LATER async eviction\n //? — drop-oldest by a future enqueue, or age expiry — still settles).\n let suppressOnDrop = true;\n //? `enqueueSyncRequest` returns `false` when the offline queue is\n //? full and `dropPolicy: 'reject'` is active. Resolve with a\n //? normalized error envelope so the caller can branch instead of\n //? awaiting forever.\n const enqueued = enqueueSyncRequest({\n id: queueId,\n key: fullName,\n run: (s) => {\n runRequest(s);\n },\n createdAt: Date.now(),\n dropPolicy: offlineDropPolicy,\n //? SYNC-09 — settle the awaiting caller when this QUEUED request is\n //? later EVICTED (drop-oldest by a future enqueue / age expiry)\n //? instead of ever running. Without it the promise would hang\n //? forever, since `run` is the only thing that could otherwise\n //? resolve it. The default `offlineQueue.dropPolicy` is\n //? `'drop-oldest'`, so async eviction is the NORMAL overflow path\n //? for high-churn syncs (cursor moves), not an edge case.\n onDrop: (reason) => {\n if (suppressOnDrop) return;\n resolve(normalizeSyncError({\n response: { status: 'error', errorCode: 'offline.dropped', errorParams: [{ key: 'reason', value: reason }] },\n fallbackErrorCode: 'offline.dropped',\n }));\n },\n });\n //? Past the synchronous enqueue window: if the item is sitting in the\n //? queue, a future eviction should settle via `onDrop`.\n suppressOnDrop = false;\n if (!enqueued) {\n resolve(normalizeSyncError({\n response: { status: 'error', errorCode: 'offline.queueFull' },\n fallbackErrorCode: 'offline.queueFull',\n }));\n }\n return;\n }\n\n const tempIndex = incrementResponseIndex();\n\n let cleanupProgressListener: (() => void) | null = null;\n //? CORE-06 — ack-timeout. Armed right after the request is emitted so a\n //? lost acknowledgement (server restart/crash between emit and reply)\n //? settles the promise with `sync.requestTimeout` (504) instead of\n //? hanging the awaiting caller forever. Mirrors `apiRequest`'s\n //? `api.requestTimeoutMs` wiring.\n let responseTimeout: ReturnType<typeof setTimeout> | null = null;\n const clearResponseTimeout = () => {\n if (responseTimeout) {\n clearTimeout(responseTimeout);\n responseTimeout = null;\n }\n };\n\n if (shouldLogDev()) {\n getLogger().debug(`Client Sync Request(${String(tempIndex)})`, { syncName: sanitizedName, data, receiver: normalizedReceiver, ignoreSelf });\n }\n\n //? S13 — server-issued cancel id. The server emits a `{ __cancelId }`\n //? handshake frame on the progress channel before any work runs; we\n //? capture it so an abort can target THIS exact request (not a callback\n //? name reused across concurrent same-route requests, which corrupted\n //? the server-side abort registry). The progress listener is ALWAYS\n //? registered now — even without `onStream` — so the handshake is caught\n //? on non-streaming routes too. The reserved `__cancelId` frame is\n //? filtered out of `onStream` delivery.\n let serverCancelId: string | null = null;\n const progressEventName = buildSyncProgressEventName(tempIndex);\n const progressListener = (streamPayload: SyncRequestStreamEvent & { __cancelId?: string }) => {\n if (typeof streamPayload.__cancelId === 'string') {\n serverCancelId = streamPayload.__cancelId;\n return;\n }\n\n if (typeof onStream !== 'function') return;\n\n if (shouldLogStream()) {\n getLogger().debug(`Server Sync Stream(${String(tempIndex)})`, { syncName: sanitizedName, streamPayload });\n }\n\n onStream(streamPayload);\n };\n\n socketInstance.on(progressEventName, progressListener);\n cleanupProgressListener = () => {\n socketInstance.off(progressEventName, progressListener);\n };\n\n //? Fallback cancel key for the narrow window before the `__cancelId`\n //? handshake frame arrives (abort fired immediately on a slow link).\n //? Best-effort only — once the handshake lands `serverCancelId` is the\n //? authoritative, per-request-unique key the server registered under.\n const syncCb = `${sanitizedName}/${resolvedVersion}`;\n socketInstance.emit(socketEventNames.sync, { name: fullName, data, cb: syncCb, receiver: normalizedReceiver, responseIndex: tempIndex, ignoreSelf });\n\n //? B1 / S13 — bridge the consumer's AbortSignal to the server. When the\n //? signal fires we emit `syncCancel { cb: <serverCancelId> }` so the\n //? server-side handler stops emitting new chunks. We also resolve\n //? locally with `request.aborted` so the awaiting caller settles.\n const responseEventName = buildSyncResponseEventName(tempIndex);\n //? Forward-declared so the abort/timeout paths can proactively `off()`\n //? the lingering ack listener instead of waiting for a reply that was\n //? cancelled / will never arrive.\n let cleanupResponseListener: (() => void) | null = null;\n\n let cleanupExternalAbort: (() => void) | null = null;\n if (externalSignal) {\n const externalAbortHandler = () => {\n clearResponseTimeout();\n socketInstance.emit(socketEventNames.syncCancel, { cb: serverCancelId ?? syncCb });\n cleanupProgressListener?.();\n cleanupProgressListener = null;\n cleanupResponseListener?.();\n cleanupResponseListener = null;\n cleanupExternalAbort?.();\n cleanupExternalAbort = null;\n resolve(normalizeSyncError({\n response: { status: 'error', errorCode: 'request.aborted' },\n fallbackErrorCode: 'request.aborted',\n }));\n };\n externalSignal.addEventListener('abort', externalAbortHandler);\n cleanupExternalAbort = () => {\n externalSignal.removeEventListener('abort', externalAbortHandler);\n };\n }\n\n //? Arm the ack-timeout. `sync.requestTimeoutMs` (default 30000) may be\n //? `false` to disable. On expiry we tear down every listener and settle\n //? with a 504 envelope so the awaiting caller never hangs.\n const effectiveTimeoutMs = getProjectConfig().sync.requestTimeoutMs;\n if (typeof effectiveTimeoutMs === 'number' && effectiveTimeoutMs > 0) {\n responseTimeout = setTimeout(() => {\n responseTimeout = null;\n cleanupProgressListener?.();\n cleanupProgressListener = null;\n cleanupResponseListener?.();\n cleanupResponseListener = null;\n cleanupExternalAbort?.();\n cleanupExternalAbort = null;\n if (shouldLogDev()) {\n getLogger().error(`Sync ${sanitizedName} timed out`, undefined, { responseIndex: tempIndex });\n }\n resolve(normalizeSyncError({\n response: { status: 'error', errorCode: 'sync.requestTimeout', httpStatus: 504 },\n fallbackErrorCode: 'sync.requestTimeout',\n }));\n }, effectiveTimeoutMs);\n }\n\n const responseListener = (responseData: SyncAckResponse) => {\n clearResponseTimeout();\n cleanupProgressListener?.();\n cleanupProgressListener = null;\n cleanupResponseListener = null;\n cleanupExternalAbort?.();\n cleanupExternalAbort = null;\n\n if (responseData.status === \"error\") {\n const normalizedError = normalizeSyncError({\n response: responseData,\n fallbackErrorCode: 'sync.failedRequest',\n });\n\n if (shouldLogDev()) {\n getLogger().error(`Sync ${sanitizedName} failed`, undefined, { message: normalizedError.message });\n }\n if (shouldNotifyDev()) {\n notify.error({\n key: 'sync.failedRequest',\n params: [\n { key: 'name', value: sanitizedName },\n { key: 'message', value: normalizedError.message },\n ],\n });\n }\n resolve(normalizedError);\n return;\n }\n\n if (responseData.status !== 'success') {\n resolve(normalizeSyncError({\n response: responseData,\n fallbackErrorCode: 'sync.invalidServerResponse',\n }));\n return;\n }\n\n const result = responseData.result && typeof responseData.result === 'object'\n ? responseData.result\n : {};\n\n //? `RequestOutput.result` is a generic `Record<string, never>` shape\n //? that the runtime response can't structurally satisfy from the\n //? untyped `responseData.result` (server-side typing lives on the\n //? other side of the socket). The cast is the documented socket\n //? response boundary.\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- socket response boundary\n resolve({\n status: 'success',\n message: typeof responseData.message === 'string' && responseData.message.trim().length > 0\n ? responseData.message\n : `sync ${sanitizedName} success`,\n result,\n } as RequestOutput);\n };\n\n socketInstance.once(responseEventName, responseListener);\n cleanupResponseListener = () => {\n socketInstance.off(responseEventName, responseListener);\n };\n };\n\n runRequest(activeSocket);\n })();\n });\n};\n\nexport function syncRequest<F extends SyncFullName, V extends VersionsForFullName<F>>(\n params: SyncRequestParamsWithOptions<F, V>\n): Promise<Prettify<SyncRequestResponseForFullName<F, V>>> {\n return syncRequestInternal(params);\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// useSyncEvents Hook - Type-Safe Event Registration\n// ═══════════════════════════════════════════════════════════════════════════════\n\n//? Hoisted to module scope so the emitted `.d.ts` for `useSyncEvents` can\n//? reference them. Local interface declarations inside an exported function\n//? trigger TS4025 (\"has or is using private name\") under `declaration: true`.\ninterface TypedCallbackParams<F extends SyncFullName, V extends VersionsForFullName<F>> {\n clientOutput: ClientOutputForFullName<F, V>;\n serverOutput: ServerOutputForFullName<F, V>;\n}\n\ninterface UpsertParams<F extends SyncFullName, V extends VersionsForFullName<F>> {\n name: F;\n version: V;\n callback: (params: TypedCallbackParams<F, V>) => void;\n}\n\ninterface UpsertStreamParams<F extends SyncFullName, V extends VersionsForFullName<F>> {\n name: F;\n version: V;\n callback: SyncRouteStreamCallbackForFullName<F, V>;\n}\n\nexport const useSyncEvents = () => {\n const localRegistryRef = useRef<Map<string, SyncEventCallback>>(new Map());\n const localStreamRegistryRef = useRef<Map<string, SyncEventStreamCallback>>(new Map());\n\n const upsertSyncEventCallback = useCallback(<F extends SyncFullName, V extends VersionsForFullName<F>>(\n params: UpsertParams<F, V>\n ): (() => void) => {\n\n if (typeof params.version !== 'string') {\n if (shouldLogDev()) {\n getLogger().error(\"Invalid version for upsertSyncEventCallback\");\n }\n if (shouldNotifyDev()) {\n notify.error({ key: 'sync.invalidVersion' });\n }\n return noop;\n }\n\n if (typeof params.callback !== 'function') {\n if (shouldLogDev()) {\n getLogger().error(\"Invalid callback for upsertSyncEventCallback\");\n }\n if (shouldNotifyDev()) {\n notify.error({ key: 'sync.invalidCallback' });\n }\n return noop;\n }\n\n const routeName = params.name;\n const parsedRoute = parseServiceRouteName(routeName);\n if (parsedRoute.status === 'error') {\n if (shouldLogDev()) {\n getLogger().error(`Invalid name for upsertSyncEventCallback`, undefined, { routeName, reason: parsedRoute.reason });\n }\n if (shouldNotifyDev()) {\n notify.error({ key: 'routing.invalidServiceRouteName' });\n }\n return noop;\n }\n const sanitizedName = parsedRoute.normalizedRouteName;\n\n const routeVersion = params.version;\n const fullName = `sync/${sanitizedName}/${routeVersion}`;\n const callback: SyncEventCallback = ({ clientOutput, serverOutput }) => {\n params.callback({\n clientOutput: clientOutput as ClientOutputForFullName<F, V>,\n serverOutput: serverOutput as ServerOutputForFullName<F, V>,\n });\n };\n const callbacks = getCallbacksForRoute(fullName);\n\n const previousForRoute = localRegistryRef.current.get(fullName);\n if (previousForRoute) {\n syncEvents[fullName] = callbacks.filter((cb) => cb !== previousForRoute);\n }\n\n const nextCallbacks = getCallbacksForRoute(fullName);\n // Multiple components can intentionally subscribe to the same sync event.\n // Only warn when the exact same callback is registered twice.\n if (nextCallbacks.includes(callback)) {\n if (shouldLogDev()) {\n getLogger().warn(`[SyncEvents] Duplicate callback registration was ignored`, { fullName });\n }\n\n localRegistryRef.current.set(fullName, callback);\n\n return () => {\n const current = getCallbacksForRoute(fullName);\n syncEvents[fullName] = current.filter((cb) => cb !== callback);\n\n if (localRegistryRef.current.get(fullName) === callback) {\n localRegistryRef.current.delete(fullName);\n }\n };\n }\n\n nextCallbacks.push(callback);\n syncEvents[fullName] = nextCallbacks;\n localRegistryRef.current.set(fullName, callback);\n\n return () => {\n const current = getCallbacksForRoute(fullName);\n syncEvents[fullName] = current.filter((cb) => cb !== callback);\n\n if (localRegistryRef.current.get(fullName) === callback) {\n localRegistryRef.current.delete(fullName);\n }\n };\n }, []);\n\n const upsertSyncEventStreamCallback = useCallback(<F extends SyncFullName, V extends VersionsForFullName<F>>(\n params: UpsertStreamParams<F, V>\n ): (() => void) => {\n\n if (typeof params.version !== 'string') {\n if (shouldLogDev()) {\n getLogger().error(\"Invalid version for upsertSyncEventStreamCallback\");\n }\n if (shouldNotifyDev()) {\n notify.error({ key: 'sync.invalidVersion' });\n }\n return noop;\n }\n\n if (typeof params.callback !== 'function') {\n if (shouldLogDev()) {\n getLogger().error(\"Invalid callback for upsertSyncEventStreamCallback\");\n }\n if (shouldNotifyDev()) {\n notify.error({ key: 'sync.invalidCallback' });\n }\n return noop;\n }\n\n const routeName = params.name;\n const parsedRoute = parseServiceRouteName(routeName);\n if (parsedRoute.status === 'error') {\n if (shouldLogDev()) {\n getLogger().error(`Invalid name for upsertSyncEventStreamCallback`, undefined, { routeName, reason: parsedRoute.reason });\n }\n if (shouldNotifyDev()) {\n notify.error({ key: 'routing.invalidServiceRouteName' });\n }\n return noop;\n }\n const sanitizedName = parsedRoute.normalizedRouteName;\n\n const routeVersion = params.version;\n const fullName = `sync/${sanitizedName}/${routeVersion}`;\n //? Combined union: both `serverStream` (from `broadcastStream` /\n //? `streamTo`) and `clientStream` (from `_client_v{n}.ts`) flow through\n //? this callback. The type matches `SyncRouteStreamCallbackForFullName`.\n const typedCallback = params.callback as (params: {\n stream: SyncRouteStreamEvent<Prettify<CombinedRouteStream<F, V> extends StreamPayload ? CombinedRouteStream<F, V> : StreamPayload>>;\n }) => void;\n const callback: SyncEventStreamCallback = ({ stream }) => {\n typedCallback({\n stream: stream as SyncRouteStreamEvent<Prettify<CombinedRouteStream<F, V> extends StreamPayload ? CombinedRouteStream<F, V> : StreamPayload>>,\n });\n };\n const callbacks = getStreamCallbacksForRoute(fullName);\n\n const previousForRoute = localStreamRegistryRef.current.get(fullName);\n if (previousForRoute) {\n syncStreamEvents[fullName] = callbacks.filter((cb) => cb !== previousForRoute);\n }\n\n const nextCallbacks = getStreamCallbacksForRoute(fullName);\n if (nextCallbacks.includes(callback)) {\n if (shouldLogDev()) {\n getLogger().warn(`[SyncEvents] Duplicate stream callback registration was ignored`, { fullName });\n }\n\n localStreamRegistryRef.current.set(fullName, callback);\n\n return () => {\n const current = getStreamCallbacksForRoute(fullName);\n syncStreamEvents[fullName] = current.filter((cb) => cb !== callback);\n\n if (localStreamRegistryRef.current.get(fullName) === callback) {\n localStreamRegistryRef.current.delete(fullName);\n }\n };\n }\n\n nextCallbacks.push(callback);\n syncStreamEvents[fullName] = nextCallbacks;\n localStreamRegistryRef.current.set(fullName, callback);\n\n return () => {\n const current = getStreamCallbacksForRoute(fullName);\n syncStreamEvents[fullName] = current.filter((cb) => cb !== callback);\n\n if (localStreamRegistryRef.current.get(fullName) === callback) {\n localStreamRegistryRef.current.delete(fullName);\n }\n };\n }, []);\n\n useEffect(() => {\n const localRegistry = localRegistryRef.current;\n const localStreamRegistry = localStreamRegistryRef.current;\n\n return () => {\n for (const [fullName, callback] of localRegistry.entries()) {\n const current = getCallbacksForRoute(fullName);\n syncEvents[fullName] = current.filter((cb) => cb !== callback);\n }\n\n for (const [fullName, callback] of localStreamRegistry.entries()) {\n const current = getStreamCallbacksForRoute(fullName);\n syncStreamEvents[fullName] = current.filter((cb) => cb !== callback);\n }\n\n localRegistry.clear();\n localStreamRegistry.clear();\n };\n }, []);\n\n return { upsertSyncEventCallback, upsertSyncEventStreamCallback };\n}\n\nexport const useSyncEventTrigger = () => {\n const triggerSyncEvent = useCallback((name: string, clientOutput: unknown = {}, serverOutput: unknown = {}) => {\n triggerSyncCallbacks(name, clientOutput, serverOutput);\n }, []);\n\n const triggerSyncStreamEvent = useCallback((name: string, stream: SyncRouteStreamEvent) => {\n triggerSyncStreamCallbacks(name, stream);\n }, []);\n\n return { triggerSyncEvent, triggerSyncStreamEvent }\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// Sync Receive Bridge — attachSyncReceiver\n// ═══════════════════════════════════════════════════════════════════════════════\n\n//? Shape of the `sync` socket event the server emits to clients. Mirrors the\n//? server's emit payload; extra keys are stream chunk fields.\ninterface SyncEventPayload {\n cb?: string;\n clientOutput?: unknown;\n serverOutput?: unknown;\n message?: string;\n status?: 'success' | 'error' | 'stream';\n fullName?: string;\n errorCode?: string;\n errorParams?: { key: string; value: string | number | boolean }[];\n httpStatus?: number;\n [key: string]: unknown;\n}\n\nconst normalizeSyncRouteKey = (value: string): string => {\n const sanitized = value.replaceAll(/^\\/+|\\/+$/g, '');\n if (sanitized.length === 0) return '';\n if (sanitized.startsWith('sync/')) return sanitized;\n return `sync/${sanitized}`;\n};\n\nconst getSyncRouteKeys = ({ fullName, cb }: { fullName?: string; cb?: string }): string[] => {\n const keys = new Set<string>();\n if (typeof fullName === 'string') {\n const normalized = normalizeSyncRouteKey(fullName);\n if (normalized) keys.add(normalized);\n }\n if (typeof cb === 'string') {\n const normalized = normalizeSyncRouteKey(cb);\n if (normalized) keys.add(normalized);\n }\n return [...keys];\n};\n\n//? Per-socket idempotency guard — attaching twice to the same socket would\n//? double-dispatch every sync event.\nconst attachedSyncSockets = new WeakSet<Socket>();\n\n//? Wire the incoming `sync` socket listener that routes server fan-out into the\n//? registered `upsertSyncEventCallback` / `upsertSyncEventStreamCallback`\n//? subscribers. Previously this logic was inlined in the consumer's\n//? `socketInitializer.ts`; relocating it here makes `@luckystack/sync` own its\n//? own receive path, so a consumer only has to dynamic-import this and call it\n//? (the add-later sync wiring is then a single line, not a copy-paste block).\nexport const attachSyncReceiver = (socketInstance: Socket): void => {\n if (attachedSyncSockets.has(socketInstance)) return;\n attachedSyncSockets.add(socketInstance);\n\n socketInstance.on(socketEventNames.sync, (payload: SyncEventPayload) => {\n const { cb, clientOutput, serverOutput, message, status, fullName, errorCode, errorParams } = payload;\n if (shouldLogDev()) {\n getLogger().debug('Server Sync Response', { payload });\n }\n\n const routeKeys = getSyncRouteKeys({ fullName, cb });\n\n if (status === 'stream') {\n if (routeKeys.length === 0) return;\n\n //? SYNC-N7 — strip all reserved envelope keys from the forwarded stream\n //? payload so a consumer handler that accidentally emits `errorCode`,\n //? `errorParams`, `httpStatus`, or `message` in a stream chunk doesn't\n //? pollute the client callback's stream argument with error-envelope fields.\n //? `status`, `fullName`, `cb` were already stripped; the others are added here.\n const streamPayload = { ...payload };\n delete streamPayload.status;\n delete streamPayload.fullName;\n delete streamPayload.cb;\n delete streamPayload.errorCode;\n delete streamPayload.errorParams;\n delete streamPayload.httpStatus;\n delete streamPayload.message;\n\n if (shouldLogStream()) {\n getLogger().debug('Server Sync Stream', { routeKeys, streamPayload });\n }\n\n for (const routeKey of routeKeys) {\n triggerSyncStreamCallbacks(routeKey, streamPayload);\n }\n return;\n }\n\n if (status === 'error') {\n if (errorCode === 'sync.ignore' || message === 'sync.ignore') return;\n if (shouldNotifyDev()) {\n if (errorCode) {\n notify.error({ key: errorCode, params: errorParams });\n } else if (message) {\n notify.error({ key: message });\n }\n }\n return;\n }\n\n if (routeKeys.length === 0) {\n const errorMessage = `Sync response is missing fullName for cb '${cb ?? 'unknown'}'.`;\n if (shouldLogDev()) {\n getLogger().error(errorMessage);\n }\n if (shouldNotifyDev()) {\n notify.error({ key: 'sync.invalidRequestFormat' });\n }\n //? SYNC-10 — degrade to a logged warning instead of throwing. This runs\n //? inside the `socket.on(sync)` listener; a `throw` here is an uncaught\n //? exception on a server-controlled (malformed) frame that can kill other\n //? listeners bound to the same emit. The error is already logged + notified\n //? above, so `return` loses no information.\n return;\n }\n\n for (const routeKey of routeKeys) {\n triggerSyncCallbacks(routeKey, clientOutput, serverOutput);\n }\n });\n};\n\ntype SocketStatusSetter = Dispatch<\n SetStateAction<{\n self: statusContent;\n [userId: string]: statusContent;\n }>\n>;\n\nconst buildConnectHandler = ({ setSocketStatus }: { setSocketStatus: SocketStatusSetter }) => () => {\n if (shouldLogSocketStatus()) getLogger().info(\"Connected to server\");\n setSocketStatus(prev => ({ ...prev, self: { ...prev.self, status: \"CONNECTED\" } }));\n};\n\nconst buildDisconnectHandler = ({ setSocketStatus }: { setSocketStatus: SocketStatusSetter }) => () => {\n setSocketStatus(prev => ({ ...prev, self: { ...prev.self, status: \"DISCONNECTED\" } }));\n if (shouldLogSocketStatus()) getLogger().info(\"Disconnected, trying to reconnect...\");\n};\n\nconst buildReconnectAttemptHandler = ({ setSocketStatus }: { setSocketStatus: SocketStatusSetter }) => (attempt: number) => {\n setSocketStatus(prev => ({ ...prev, self: { ...prev.self, status: \"RECONNECTING\", reconnectAttempt: attempt } }));\n if (shouldLogSocketStatus()) getLogger().info(`Reconnecting attempt ${String(attempt)}...`);\n};\n\nconst buildUserAfkHandler = ({\n setSocketStatus, sessionRef,\n}: {\n setSocketStatus: SocketStatusSetter;\n sessionRef: RefObject<SessionLayout | null>;\n}) => ({ userId, endTime }: { userId: string; endTime?: number }) => {\n if (sessionRef.current !== null && userId === sessionRef.current.id) {\n setSocketStatus(prev => ({\n ...prev,\n self: { status: \"DISCONNECTED\", reconnectAttempt: undefined, endTime },\n }));\n } else {\n setSocketStatus(prev => ({\n ...prev,\n [userId]: { status: \"DISCONNECTED\", endTime },\n }));\n }\n};\n\nconst buildUserBackHandler = ({ setSocketStatus }: { setSocketStatus: SocketStatusSetter }) => ({ userId }: { userId: string }) => {\n if (shouldLogSocketStatus()) getLogger().debug(\"userBack\", { userId });\n setSocketStatus(prev => ({\n ...prev,\n [userId]: { status: \"CONNECTED\", endTime: undefined },\n }));\n};\n\nconst buildConnectErrorHandler = ({ setSocketStatus }: { setSocketStatus: SocketStatusSetter }) => (err: { message: string }) => {\n if (shouldLogSocketStatus()) getLogger().debug(\"connect_error\", { err });\n setSocketStatus(prev => ({\n ...prev,\n self: { ...prev.self, status: \"DISCONNECTED\", reconnectAttempt: undefined },\n }));\n if (shouldLogDev()) getLogger().error(`Connection error`, err);\n if (shouldNotifyDev()) notify.error({ key: 'common.connectionError' });\n};\n\n//? SYNC-N6 — `initSyncRequest` owns the canonical lifecycle listeners\n//? (`connect`, `disconnect`, `reconnect_attempt`, `userAfk`, `userBack`,\n//? `connect_error`). A consumer's `socketInitializer.ts` that also registers\n//? `disconnect`/`reconnect_attempt` on the same socket instance creates\n//? duplicate handlers — both fire on every event. Since `initSyncRequest` is\n//? idempotent (tears down the previous set before re-binding), consumer code\n//? should delegate lifecycle status updates here and avoid re-registering\n//? those events directly. Duplicate `console.log` in the consumer is harmless\n//? but produces double log lines.\nexport const initSyncRequest = async ({\n setSocketStatus,\n sessionRef\n}: {\n setSocketStatus: SocketStatusSetter;\n sessionRef: RefObject<SessionLayout | null> | null;\n}) => {\n\n if (!await waitForSocket()) { return; }\n if (!socket) { return; }\n if (!sessionRef) { return; }\n\n if (activeLifecycleHandlers) {\n socket.off(socketEventNames.connect, activeLifecycleHandlers.connect);\n socket.off(socketEventNames.disconnect, activeLifecycleHandlers.disconnect);\n socket.off(socketEventNames.reconnectAttempt, activeLifecycleHandlers.reconnectAttempt);\n socket.off(socketEventNames.userAfk, activeLifecycleHandlers.userAfk);\n socket.off(socketEventNames.userBack, activeLifecycleHandlers.userBack);\n socket.off(socketEventNames.connectError, activeLifecycleHandlers.connectError);\n }\n\n const connect = buildConnectHandler({ setSocketStatus });\n const disconnect = buildDisconnectHandler({ setSocketStatus });\n const reconnectAttempt = buildReconnectAttemptHandler({ setSocketStatus });\n const userAfk = buildUserAfkHandler({ setSocketStatus, sessionRef });\n const userBack = buildUserBackHandler({ setSocketStatus });\n const connectError = buildConnectErrorHandler({ setSocketStatus });\n\n activeLifecycleHandlers = {\n connect,\n disconnect,\n reconnectAttempt,\n userAfk,\n userBack,\n connectError,\n };\n\n socket.on(socketEventNames.connect, connect);\n socket.on(socketEventNames.disconnect, disconnect);\n socket.on(socketEventNames.reconnectAttempt, reconnectAttempt);\n socket.on(socketEventNames.userAfk, userAfk);\n socket.on(socketEventNames.userBack, userBack);\n socket.on(socketEventNames.connectError, connectError);\n\n}"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/syncRequest.ts"],"sourcesContent":["//? All cross-package imports go through `@luckystack/core/client` (the\n//? browser-safe subpath) — never the main `@luckystack/core` barrel which\n//? re-exports server-only modules (paths.ts, db.ts, redis.ts) that must\n//? not enter a Vite client bundle.\nimport type {\n BaseSessionLayout as SessionLayout,\n StreamPayload,\n SyncTypeMap,\n statusContent,\n} from \"@luckystack/core/client\";\nimport {\n getLogger,\n getProjectConfig,\n notify,\n incrementResponseIndex,\n socket,\n waitForSocket,\n enqueueSyncRequest,\n isOnline,\n normalizeErrorResponseCore,\n parseServiceRouteName,\n buildSyncProgressEventName,\n buildSyncResponseEventName,\n socketEventNames,\n} from \"@luckystack/core/client\";\nimport { Dispatch, RefObject, SetStateAction, useCallback, useEffect, useRef } from \"react\";\nimport { Socket } from \"socket.io-client\";\n\nexport type SyncRequestStreamEvent<T extends StreamPayload = StreamPayload> = T;\n\nexport type SyncRouteStreamEvent<T extends StreamPayload = StreamPayload> = T;\n\ntype SyncRequestStreamCallback = (event: SyncRequestStreamEvent) => void;\ntype SyncEventStreamCallback = (params: { stream: SyncRouteStreamEvent }) => void;\n\nconst shouldLogDev = () => getProjectConfig().logging.devLogs;\nconst shouldNotifyDev = () => getProjectConfig().logging.devNotifications;\nconst shouldLogSocketStatus = () => getProjectConfig().logging.socketStatus;\nconst shouldLogStream = () => getProjectConfig().logging.stream;\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// Type Helpers for Sync Requests\n// ═══════════════════════════════════════════════════════════════════════════════\n\n// Check if data input is required (i.e., T does NOT allow empty object)\n// Unions like {a:1} | {b:1} do NOT allow {}, so data will be required\ntype DataRequired<T> = Record<string, never> extends T ? false : true;\n\ntype UnionToIntersection<U> =\n (U extends unknown ? (arg: U) => void : never) extends ((arg: infer I) => void)\n ? I\n : never;\n\ntype Prettify<T> = { [K in keyof T]: T[K] } & {};\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// Global Sync Params\n// ═══════════════════════════════════════════════════════════════════════════════\n\n// All possible sync names across all pages\ntype SyncRouteRecord = UnionToIntersection<{\n [P in keyof SyncTypeMap]: {\n [N in keyof SyncTypeMap[P] as P extends 'root'\n ? `system/${Extract<N, string>}`\n : `${Extract<P, string>}/${Extract<N, string>}`]: SyncTypeMap[P][N]\n }\n}[keyof SyncTypeMap]>;\n\ntype SyncFullName = Extract<keyof SyncRouteRecord, string>;\ntype VersionsForFullName<F extends SyncFullName> = Extract<keyof SyncRouteRecord[F], string>;\n\ntype ClientInputForFullName<F extends SyncFullName, V extends VersionsForFullName<F>> = SyncRouteRecord[F][V] extends { clientInput: infer I }\n ? I\n : never;\n\ntype ServerOutputForFullName<F extends SyncFullName, V extends VersionsForFullName<F>> = SyncRouteRecord[F][V] extends { serverOutput: infer O }\n ? O\n : never;\n\ntype ClientOutputForFullName<F extends SyncFullName, V extends VersionsForFullName<F>> = SyncRouteRecord[F][V] extends { clientOutput: infer O }\n ? O\n : never;\n\ntype ServerStreamForFullName<F extends SyncFullName, V extends VersionsForFullName<F>> = SyncRouteRecord[F][V] extends { serverStream: infer O }\n ? O\n : never;\n\ntype ClientStreamForFullName<F extends SyncFullName, V extends VersionsForFullName<F>> = SyncRouteRecord[F][V] extends { clientStream: infer O }\n ? O\n : never;\n\ntype SyncRequestStreamCallbackForFullName<F extends SyncFullName, V extends VersionsForFullName<F>> =\n [ServerStreamForFullName<F, V>] extends [never]\n ? never\n : (event: SyncRequestStreamEvent<Prettify<ServerStreamForFullName<F, V> extends StreamPayload ? ServerStreamForFullName<F, V> : StreamPayload>>) => void;\n\n//? Recipients see chunks from THREE sources, all on the same wire channel:\n//? 1. `_client_v{n}.ts`'s `stream(...)` (per-recipient, runs after _server)\n//? 2. `_server_v{n}.ts`'s `broadcastStream(...)` (room-wide fan-out)\n//? 3. `_server_v{n}.ts`'s `streamTo(tokens, ...)` (selective fan-out)\n//? All three flow into `upsertSyncEventCallback`'s `stream` argument. Both\n//? serverStream and clientStream are folded into the union so the callback\n//? sees every shape the route can emit. If neither side ever streams, the\n//? callback type collapses to never (compile error to register one).\ntype CombinedRouteStream<F extends SyncFullName, V extends VersionsForFullName<F>> =\n | (ClientStreamForFullName<F, V> extends never ? never : ClientStreamForFullName<F, V>)\n | (ServerStreamForFullName<F, V> extends never ? never : ServerStreamForFullName<F, V>);\n\ntype SyncRouteStreamCallbackForFullName<F extends SyncFullName, V extends VersionsForFullName<F>> =\n [CombinedRouteStream<F, V>] extends [never]\n ? never\n : (params: { stream: SyncRouteStreamEvent<Prettify<CombinedRouteStream<F, V> extends StreamPayload ? CombinedRouteStream<F, V> : StreamPayload>> }) => void;\n\ntype SyncParamsForFullName<\n F extends SyncFullName,\n V extends VersionsForFullName<F>\n> = DataRequired<ClientInputForFullName<F, V>> extends true\n ? {\n name: F;\n version: V;\n data: ClientInputForFullName<F, V>;\n receiver: string;\n ignoreSelf?: boolean;\n onStream?: SyncRequestStreamCallbackForFullName<F, V>;\n /**\n * Per-request override of `projectConfig.offlineQueue.dropPolicy`. Lets a\n * specific sync (\"editor cursor move\") pick `'drop-oldest'` while the\n * app default stays `'reject'` for safer sends. When omitted, falls back\n * to the global config.\n */\n offlineDropPolicy?: 'drop-oldest' | 'drop-newest' | 'reject';\n /**\n * Optional AbortSignal. When aborted the client emits `syncCancel { cb }`\n * to the server and resolves locally with\n * `{ status: 'error', errorCode: 'request.aborted' }`.\n */\n signal?: AbortSignal;\n }\n : {\n name: F;\n version: V;\n data?: ClientInputForFullName<F, V>;\n receiver: string;\n ignoreSelf?: boolean;\n onStream?: SyncRequestStreamCallbackForFullName<F, V>;\n /** Per-request override (see typed branch). */\n offlineDropPolicy?: 'drop-oldest' | 'drop-newest' | 'reject';\n /** Optional AbortSignal — see typed branch. */\n signal?: AbortSignal;\n };\n\ninterface RuntimeSyncParams {\n name?: string;\n version?: string;\n data?: unknown;\n receiver?: string;\n ignoreSelf?: boolean;\n onStream?: SyncRequestStreamCallback;\n offlineDropPolicy?: 'drop-oldest' | 'drop-newest' | 'reject';\n signal?: AbortSignal;\n}\n\ninterface SyncErrorParam { key: string; value: string | number | boolean };\n\ninterface SyncResponseError {\n status: 'error';\n message: string;\n errorCode: string;\n errorParams?: SyncErrorParam[];\n httpStatus?: number;\n}\n\ninterface SyncAckResponse {\n status?: 'success' | 'error';\n message?: string;\n result?: unknown;\n errorCode?: string;\n errorParams?: SyncErrorParam[];\n httpStatus?: number;\n}\n\ntype SyncResultForFullName<F extends SyncFullName, V extends VersionsForFullName<F>> =\n [ServerOutputForFullName<F, V>] extends [never]\n ? Record<string, never>\n : ServerOutputForFullName<F, V>;\n\ntype SyncRequestResponseForFullName<F extends SyncFullName, V extends VersionsForFullName<F>> =\n | SyncResponseError\n | {\n status: 'success';\n message: string;\n result: SyncResultForFullName<F, V>;\n };\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// Sync Event Callbacks Registry\n// ═══════════════════════════════════════════════════════════════════════════════\n\ntype SyncEventCallback = (params: { clientOutput: unknown; serverOutput: unknown }) => void;\nconst syncEvents: Partial<Record<string, SyncEventCallback[]>> = {};\nconst syncStreamEvents: Partial<Record<string, SyncEventStreamCallback[]>> = {};\nconst noop = () => null;\n\ninterface SyncLifecycleHandlers {\n connect: () => void;\n disconnect: () => void;\n reconnectAttempt: (attempt: number) => void;\n userAfk: (payload: { userId: string; endTime?: number }) => void;\n userBack: (payload: { userId: string }) => void;\n connectError: (err: { message: string }) => void;\n}\n\nlet activeLifecycleHandlers: SyncLifecycleHandlers | null = null;\n\nconst canSendNow = (socketInstance: Socket) => {\n if (!socketInstance.connected) return false;\n return isOnline();\n};\n\nconst createQueueId = () => {\n return `${String(Date.now())}-${String(Math.random())}`;\n};\n\nconst getCallbacksForRoute = (route: string): SyncEventCallback[] => {\n syncEvents[route] ??= [];\n return syncEvents[route];\n};\n\nconst getStreamCallbacksForRoute = (route: string): SyncEventStreamCallback[] => {\n syncStreamEvents[route] ??= [];\n return syncStreamEvents[route];\n};\n\nconst triggerSyncCallbacks = (name: string, clientOutput: unknown, serverOutput: unknown) => {\n const callbacks = syncEvents[name] ?? [];\n if (callbacks.length === 0) {\n //? Streaming-only routes legitimately register ONLY a stream callback —\n //? the final success envelope is implicit in the stream completing. Only\n //? warn when NEITHER callback type is registered for the route.\n const streamCallbacks = syncStreamEvents[name] ?? [];\n if (streamCallbacks.length === 0 && shouldLogDev()) {\n getLogger().warn(`Sync event ${name} has no registered callback on this page`);\n }\n return;\n }\n\n for (const callback of callbacks) {\n callback({ clientOutput, serverOutput });\n }\n};\n\nconst triggerSyncStreamCallbacks = (name: string, stream: SyncRouteStreamEvent) => {\n const callbacks = syncStreamEvents[name] ?? [];\n if (callbacks.length === 0) {\n return;\n }\n\n for (const callback of callbacks) {\n callback({ stream });\n }\n};\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// syncRequest Function Overloads\n// ═══════════════════════════════════════════════════════════════════════════════\n\nconst normalizeSyncError = ({\n response,\n fallbackErrorCode,\n}: {\n response: SyncAckResponse;\n fallbackErrorCode: string;\n}): SyncResponseError => {\n const normalized = normalizeErrorResponseCore({\n response,\n fallbackErrorCode,\n resolveMessage: ({ errorCode }) => {\n if (typeof response.message === 'string' && response.message.trim().length > 0) {\n return response.message;\n }\n\n return errorCode;\n },\n });\n\n return {\n status: 'error',\n message: normalized.message,\n errorCode: normalized.errorCode,\n errorParams: normalized.errorParams,\n httpStatus: normalized.httpStatus,\n };\n};\n\ntype SyncRequestParamsWithOptions<F extends SyncFullName, V extends VersionsForFullName<F>> =\n SyncParamsForFullName<F, V>;\n\n//? Outcome of the pre-flight validation phase of a sync send. Either a\n//? normalized error envelope to resolve the request with, or the sanitized\n//? values the send phase needs. Extracted from `syncRequestInternal` so the\n//? \"validate first, execute second\" split is explicit (the orchestrator stays\n//? a thin promise wrapper around prepare → run).\ntype PreparedSyncRequest =\n | { ok: false; error: SyncResponseError }\n | { ok: true; sanitizedName: string; version: string; data: object; normalizedReceiver: string };\n\n//? Pure-ish validation chain (logs + dev-notifies on failure, same as the\n//? previous inlined block). Async because the socket-readiness gate\n//? (`waitForSocket`) is awaited last, after the cheap structural checks. Each\n//? failure path returns the SAME normalized error envelope the inlined code\n//? resolved with — behaviour-identical.\nconst prepareSyncRequest = async ({\n name,\n version,\n receiver,\n payloadData,\n}: {\n name: unknown;\n version: unknown;\n receiver: unknown;\n payloadData: unknown;\n}): Promise<PreparedSyncRequest> => {\n if (!name || typeof name !== \"string\") {\n if (shouldLogDev()) {\n getLogger().error(\"Invalid name for syncRequest\");\n }\n if (shouldNotifyDev()) {\n notify.error({ key: 'sync.invalidName' });\n }\n return { ok: false, error: normalizeSyncError({\n response: { status: 'error', errorCode: 'sync.invalidName' },\n fallbackErrorCode: 'sync.invalidName',\n }) };\n }\n\n const parsedRoute = parseServiceRouteName(name);\n if (parsedRoute.status === 'error') {\n if (shouldLogDev()) {\n getLogger().error(`[syncRequest] Invalid service route name '${name}'`, undefined, { reason: parsedRoute.reason });\n }\n if (shouldNotifyDev()) {\n notify.error({ key: 'routing.invalidServiceRouteName' });\n }\n return { ok: false, error: normalizeSyncError({\n response: {\n status: 'error',\n errorCode: 'routing.invalidServiceRouteName',\n errorParams: [{ key: 'name', value: name }],\n },\n fallbackErrorCode: 'routing.invalidServiceRouteName',\n }) };\n }\n\n const sanitizedName = parsedRoute.normalizedRouteName;\n\n const data = payloadData && typeof payloadData === \"object\" ? payloadData : {};\n\n if (!version || typeof version !== 'string') {\n if (shouldLogDev()) {\n getLogger().error(\"Invalid version for syncRequest\");\n }\n if (shouldNotifyDev()) {\n notify.error({ key: 'sync.invalidVersion' });\n }\n return { ok: false, error: normalizeSyncError({\n response: { status: 'error', errorCode: 'sync.invalidVersion' },\n fallbackErrorCode: 'sync.invalidVersion',\n }) };\n }\n\n const normalizedReceiver = typeof receiver === 'string' ? receiver.trim() : '';\n\n if (!normalizedReceiver) {\n if (shouldLogDev()) {\n getLogger().error(\"You need to provide a receiver for syncRequest, this can be either 'all' to trigger all sockets which we do not recommend or it can be any value such as a code e.g 'Ag2cg4'. this works together with the joinRoom and leaveRoom function\");\n }\n if (shouldNotifyDev()) {\n notify.error({ key: 'sync.missingReceiver' });\n }\n return { ok: false, error: normalizeSyncError({\n response: { status: 'error', errorCode: 'sync.missingReceiver' },\n fallbackErrorCode: 'sync.missingReceiver',\n }) };\n }\n\n if (!await waitForSocket()) {\n return { ok: false, error: normalizeSyncError({\n response: { status: 'error', errorCode: 'sync.ioUnavailable' },\n fallbackErrorCode: 'sync.ioUnavailable',\n }) };\n }\n if (!socket) {\n return { ok: false, error: normalizeSyncError({\n response: { status: 'error', errorCode: 'sync.ioUnavailable' },\n fallbackErrorCode: 'sync.ioUnavailable',\n }) };\n }\n\n return { ok: true, sanitizedName, version, data, normalizedReceiver };\n};\n\nconst syncRequestInternal = <F extends SyncFullName, V extends VersionsForFullName<F>>(\n params: SyncRequestParamsWithOptions<F, V>\n): Promise<Prettify<SyncRequestResponseForFullName<F, V>>> => {\n const runtimeParams = params as RuntimeSyncParams;\n const { name, version, receiver, ignoreSelf, onStream, offlineDropPolicy, signal: externalSignal } = runtimeParams;\n const payloadData = runtimeParams.data;\n\n type RequestOutput = Prettify<SyncRequestResponseForFullName<F, V>>;\n\n return new Promise<RequestOutput>((resolve) => {\n void (async () => {\n //? B1 — if the consumer-supplied signal is already aborted at call\n //? time, short-circuit before we even touch the socket.\n if (externalSignal?.aborted) {\n resolve(normalizeSyncError({\n response: { status: 'error', errorCode: 'request.aborted' },\n fallbackErrorCode: 'request.aborted',\n }));\n return;\n }\n\n const prepared = await prepareSyncRequest({ name, version, receiver, payloadData });\n if (!prepared.ok) {\n resolve(prepared.error);\n return;\n }\n const { sanitizedName, version: resolvedVersion, data, normalizedReceiver } = prepared;\n\n //? Re-narrow `socket` for the closures below. `prepareSyncRequest`\n //? already guaranteed it is non-null (returns an `ioUnavailable` error\n //? otherwise), but that guard lives in another function so TS can't\n //? carry the narrowing across the call boundary.\n const activeSocket = socket;\n if (!activeSocket) {\n resolve(normalizeSyncError({\n response: { status: 'error', errorCode: 'sync.ioUnavailable' },\n fallbackErrorCode: 'sync.ioUnavailable',\n }));\n return;\n }\n\n const fullName = `sync/${sanitizedName}/${resolvedVersion}`;\n let queueId: string | null = null;\n\n const runRequest = (socketInstance: Socket) => {\n if (!canSendNow(socketInstance)) {\n queueId ??= createQueueId();\n //? For the immediate reject-new path (`drop-newest` / `reject`\n //? policy) THIS item's `onDrop` fires SYNCHRONOUSLY inside\n //? `enqueueSyncRequest`. We keep surfacing that as the historical\n //? `offline.queueFull` via the `!enqueued` branch, so suppress the\n //? `onDrop` resolve during the synchronous enqueue window and re-arm\n //? it only if the item was actually queued (so a LATER async eviction\n //? — drop-oldest by a future enqueue, or age expiry — still settles).\n let suppressOnDrop = true;\n //? `enqueueSyncRequest` returns `false` when the offline queue is\n //? full and `dropPolicy: 'reject'` is active. Resolve with a\n //? normalized error envelope so the caller can branch instead of\n //? awaiting forever.\n const enqueued = enqueueSyncRequest({\n id: queueId,\n key: fullName,\n run: (s) => {\n runRequest(s);\n },\n createdAt: Date.now(),\n dropPolicy: offlineDropPolicy,\n //? SYNC-09 — settle the awaiting caller when this QUEUED request is\n //? later EVICTED (drop-oldest by a future enqueue / age expiry)\n //? instead of ever running. Without it the promise would hang\n //? forever, since `run` is the only thing that could otherwise\n //? resolve it. The default `offlineQueue.dropPolicy` is\n //? `'drop-oldest'`, so async eviction is the NORMAL overflow path\n //? for high-churn syncs (cursor moves), not an edge case.\n onDrop: (reason) => {\n if (suppressOnDrop) return;\n resolve(normalizeSyncError({\n response: { status: 'error', errorCode: 'offline.dropped', errorParams: [{ key: 'reason', value: reason }] },\n fallbackErrorCode: 'offline.dropped',\n }));\n },\n });\n //? Past the synchronous enqueue window: if the item is sitting in the\n //? queue, a future eviction should settle via `onDrop`.\n suppressOnDrop = false;\n if (!enqueued) {\n resolve(normalizeSyncError({\n response: { status: 'error', errorCode: 'offline.queueFull' },\n fallbackErrorCode: 'offline.queueFull',\n }));\n }\n return;\n }\n\n const tempIndex = incrementResponseIndex();\n\n let cleanupProgressListener: (() => void) | null = null;\n //? CORE-06 — ack-timeout. Armed right after the request is emitted so a\n //? lost acknowledgement (server restart/crash between emit and reply)\n //? settles the promise with `sync.requestTimeout` (504) instead of\n //? hanging the awaiting caller forever. Mirrors `apiRequest`'s\n //? `api.requestTimeoutMs` wiring.\n let responseTimeout: ReturnType<typeof setTimeout> | null = null;\n const clearResponseTimeout = () => {\n if (responseTimeout) {\n clearTimeout(responseTimeout);\n responseTimeout = null;\n }\n };\n\n if (shouldLogDev()) {\n getLogger().debug(`Client Sync Request(${String(tempIndex)})`, { syncName: sanitizedName, data, receiver: normalizedReceiver, ignoreSelf });\n }\n\n //? S13 — server-issued cancel id. The server emits a `{ __cancelId }`\n //? handshake frame on the progress channel before any work runs; we\n //? capture it so an abort can target THIS exact request (not a callback\n //? name reused across concurrent same-route requests, which corrupted\n //? the server-side abort registry). The progress listener is ALWAYS\n //? registered now — even without `onStream` — so the handshake is caught\n //? on non-streaming routes too. The reserved `__cancelId` frame is\n //? filtered out of `onStream` delivery.\n let serverCancelId: string | null = null;\n const progressEventName = buildSyncProgressEventName(tempIndex);\n const progressListener = (streamPayload: SyncRequestStreamEvent & { __cancelId?: string }) => {\n if (typeof streamPayload.__cancelId === 'string') {\n serverCancelId = streamPayload.__cancelId;\n return;\n }\n\n if (typeof onStream !== 'function') return;\n\n if (shouldLogStream()) {\n getLogger().debug(`Server Sync Stream(${String(tempIndex)})`, { syncName: sanitizedName, streamPayload });\n }\n\n onStream(streamPayload);\n };\n\n socketInstance.on(progressEventName, progressListener);\n cleanupProgressListener = () => {\n socketInstance.off(progressEventName, progressListener);\n };\n\n //? Fallback cancel key for the narrow window before the `__cancelId`\n //? handshake frame arrives (abort fired immediately on a slow link).\n //? Best-effort only — once the handshake lands `serverCancelId` is the\n //? authoritative, per-request-unique key the server registered under.\n const syncCb = `${sanitizedName}/${resolvedVersion}`;\n socketInstance.emit(socketEventNames.sync, { name: fullName, data, cb: syncCb, receiver: normalizedReceiver, responseIndex: tempIndex, ignoreSelf });\n\n //? B1 / S13 — bridge the consumer's AbortSignal to the server. When the\n //? signal fires we emit `syncCancel { cb: <serverCancelId> }` so the\n //? server-side handler stops emitting new chunks. We also resolve\n //? locally with `request.aborted` so the awaiting caller settles.\n const responseEventName = buildSyncResponseEventName(tempIndex);\n //? Forward-declared so the abort/timeout paths can proactively `off()`\n //? the lingering ack listener instead of waiting for a reply that was\n //? cancelled / will never arrive.\n let cleanupResponseListener: (() => void) | null = null;\n\n let cleanupExternalAbort: (() => void) | null = null;\n if (externalSignal) {\n const externalAbortHandler = () => {\n clearResponseTimeout();\n socketInstance.emit(socketEventNames.syncCancel, { cb: serverCancelId ?? syncCb });\n cleanupProgressListener?.();\n cleanupProgressListener = null;\n cleanupResponseListener?.();\n cleanupResponseListener = null;\n cleanupExternalAbort?.();\n cleanupExternalAbort = null;\n resolve(normalizeSyncError({\n response: { status: 'error', errorCode: 'request.aborted' },\n fallbackErrorCode: 'request.aborted',\n }));\n };\n externalSignal.addEventListener('abort', externalAbortHandler);\n cleanupExternalAbort = () => {\n externalSignal.removeEventListener('abort', externalAbortHandler);\n };\n }\n\n //? Arm the ack-timeout. `sync.requestTimeoutMs` (default 30000) may be\n //? `false` to disable. On expiry we tear down every listener and settle\n //? with a 504 envelope so the awaiting caller never hangs.\n const effectiveTimeoutMs = getProjectConfig().sync.requestTimeoutMs;\n if (typeof effectiveTimeoutMs === 'number' && effectiveTimeoutMs > 0) {\n responseTimeout = setTimeout(() => {\n responseTimeout = null;\n //? SYNC — on ack-timeout, tell the server to STOP the in-flight work\n //? before tearing down locally. Previously the timeout only cleaned up\n //? the client listeners + settled 504, leaving the `_server` handler\n //? (and any fanout/streaming) running to completion uncancelled. The\n //? `__cancelId` handshake has long since arrived by the timeout window\n //? (the server emits it immediately), so `serverCancelId` is the\n //? authoritative key here; `syncCb` is only the sub-RTT fallback.\n socketInstance.emit(socketEventNames.syncCancel, { cb: serverCancelId ?? syncCb });\n cleanupProgressListener?.();\n cleanupProgressListener = null;\n cleanupResponseListener?.();\n cleanupResponseListener = null;\n cleanupExternalAbort?.();\n cleanupExternalAbort = null;\n if (shouldLogDev()) {\n getLogger().error(`Sync ${sanitizedName} timed out`, undefined, { responseIndex: tempIndex });\n }\n resolve(normalizeSyncError({\n response: { status: 'error', errorCode: 'sync.requestTimeout', httpStatus: 504 },\n fallbackErrorCode: 'sync.requestTimeout',\n }));\n }, effectiveTimeoutMs);\n }\n\n const responseListener = (responseData: SyncAckResponse) => {\n clearResponseTimeout();\n cleanupProgressListener?.();\n cleanupProgressListener = null;\n cleanupResponseListener = null;\n cleanupExternalAbort?.();\n cleanupExternalAbort = null;\n\n if (responseData.status === \"error\") {\n const normalizedError = normalizeSyncError({\n response: responseData,\n fallbackErrorCode: 'sync.failedRequest',\n });\n\n if (shouldLogDev()) {\n getLogger().error(`Sync ${sanitizedName} failed`, undefined, { message: normalizedError.message });\n }\n if (shouldNotifyDev()) {\n notify.error({\n key: 'sync.failedRequest',\n params: [\n { key: 'name', value: sanitizedName },\n { key: 'message', value: normalizedError.message },\n ],\n });\n }\n resolve(normalizedError);\n return;\n }\n\n if (responseData.status !== 'success') {\n resolve(normalizeSyncError({\n response: responseData,\n fallbackErrorCode: 'sync.invalidServerResponse',\n }));\n return;\n }\n\n const result = responseData.result && typeof responseData.result === 'object'\n ? responseData.result\n : {};\n\n //? `RequestOutput.result` is a generic `Record<string, never>` shape\n //? that the runtime response can't structurally satisfy from the\n //? untyped `responseData.result` (server-side typing lives on the\n //? other side of the socket). The cast is the documented socket\n //? response boundary.\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- socket response boundary\n resolve({\n status: 'success',\n message: typeof responseData.message === 'string' && responseData.message.trim().length > 0\n ? responseData.message\n : `sync ${sanitizedName} success`,\n result,\n } as RequestOutput);\n };\n\n socketInstance.once(responseEventName, responseListener);\n cleanupResponseListener = () => {\n socketInstance.off(responseEventName, responseListener);\n };\n };\n\n runRequest(activeSocket);\n })();\n });\n};\n\nexport function syncRequest<F extends SyncFullName, V extends VersionsForFullName<F>>(\n params: SyncRequestParamsWithOptions<F, V>\n): Promise<Prettify<SyncRequestResponseForFullName<F, V>>> {\n return syncRequestInternal(params);\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// useSyncEvents Hook - Type-Safe Event Registration\n// ═══════════════════════════════════════════════════════════════════════════════\n\n//? Hoisted to module scope so the emitted `.d.ts` for `useSyncEvents` can\n//? reference them. Local interface declarations inside an exported function\n//? trigger TS4025 (\"has or is using private name\") under `declaration: true`.\ninterface TypedCallbackParams<F extends SyncFullName, V extends VersionsForFullName<F>> {\n clientOutput: ClientOutputForFullName<F, V>;\n serverOutput: ServerOutputForFullName<F, V>;\n}\n\ninterface UpsertParams<F extends SyncFullName, V extends VersionsForFullName<F>> {\n name: F;\n version: V;\n callback: (params: TypedCallbackParams<F, V>) => void;\n}\n\ninterface UpsertStreamParams<F extends SyncFullName, V extends VersionsForFullName<F>> {\n name: F;\n version: V;\n callback: SyncRouteStreamCallbackForFullName<F, V>;\n}\n\nexport const useSyncEvents = () => {\n const localRegistryRef = useRef<Map<string, SyncEventCallback>>(new Map());\n const localStreamRegistryRef = useRef<Map<string, SyncEventStreamCallback>>(new Map());\n\n const upsertSyncEventCallback = useCallback(<F extends SyncFullName, V extends VersionsForFullName<F>>(\n params: UpsertParams<F, V>\n ): (() => void) => {\n\n if (typeof params.version !== 'string') {\n if (shouldLogDev()) {\n getLogger().error(\"Invalid version for upsertSyncEventCallback\");\n }\n if (shouldNotifyDev()) {\n notify.error({ key: 'sync.invalidVersion' });\n }\n return noop;\n }\n\n if (typeof params.callback !== 'function') {\n if (shouldLogDev()) {\n getLogger().error(\"Invalid callback for upsertSyncEventCallback\");\n }\n if (shouldNotifyDev()) {\n notify.error({ key: 'sync.invalidCallback' });\n }\n return noop;\n }\n\n const routeName = params.name;\n const parsedRoute = parseServiceRouteName(routeName);\n if (parsedRoute.status === 'error') {\n if (shouldLogDev()) {\n getLogger().error(`Invalid name for upsertSyncEventCallback`, undefined, { routeName, reason: parsedRoute.reason });\n }\n if (shouldNotifyDev()) {\n notify.error({ key: 'routing.invalidServiceRouteName' });\n }\n return noop;\n }\n const sanitizedName = parsedRoute.normalizedRouteName;\n\n const routeVersion = params.version;\n const fullName = `sync/${sanitizedName}/${routeVersion}`;\n const callback: SyncEventCallback = ({ clientOutput, serverOutput }) => {\n params.callback({\n clientOutput: clientOutput as ClientOutputForFullName<F, V>,\n serverOutput: serverOutput as ServerOutputForFullName<F, V>,\n });\n };\n const callbacks = getCallbacksForRoute(fullName);\n\n const previousForRoute = localRegistryRef.current.get(fullName);\n if (previousForRoute) {\n syncEvents[fullName] = callbacks.filter((cb) => cb !== previousForRoute);\n }\n\n const nextCallbacks = getCallbacksForRoute(fullName);\n // Multiple components can intentionally subscribe to the same sync event.\n // Only warn when the exact same callback is registered twice.\n if (nextCallbacks.includes(callback)) {\n if (shouldLogDev()) {\n getLogger().warn(`[SyncEvents] Duplicate callback registration was ignored`, { fullName });\n }\n\n localRegistryRef.current.set(fullName, callback);\n\n return () => {\n const current = getCallbacksForRoute(fullName);\n syncEvents[fullName] = current.filter((cb) => cb !== callback);\n\n if (localRegistryRef.current.get(fullName) === callback) {\n localRegistryRef.current.delete(fullName);\n }\n };\n }\n\n nextCallbacks.push(callback);\n syncEvents[fullName] = nextCallbacks;\n localRegistryRef.current.set(fullName, callback);\n\n return () => {\n const current = getCallbacksForRoute(fullName);\n syncEvents[fullName] = current.filter((cb) => cb !== callback);\n\n if (localRegistryRef.current.get(fullName) === callback) {\n localRegistryRef.current.delete(fullName);\n }\n };\n }, []);\n\n const upsertSyncEventStreamCallback = useCallback(<F extends SyncFullName, V extends VersionsForFullName<F>>(\n params: UpsertStreamParams<F, V>\n ): (() => void) => {\n\n if (typeof params.version !== 'string') {\n if (shouldLogDev()) {\n getLogger().error(\"Invalid version for upsertSyncEventStreamCallback\");\n }\n if (shouldNotifyDev()) {\n notify.error({ key: 'sync.invalidVersion' });\n }\n return noop;\n }\n\n if (typeof params.callback !== 'function') {\n if (shouldLogDev()) {\n getLogger().error(\"Invalid callback for upsertSyncEventStreamCallback\");\n }\n if (shouldNotifyDev()) {\n notify.error({ key: 'sync.invalidCallback' });\n }\n return noop;\n }\n\n const routeName = params.name;\n const parsedRoute = parseServiceRouteName(routeName);\n if (parsedRoute.status === 'error') {\n if (shouldLogDev()) {\n getLogger().error(`Invalid name for upsertSyncEventStreamCallback`, undefined, { routeName, reason: parsedRoute.reason });\n }\n if (shouldNotifyDev()) {\n notify.error({ key: 'routing.invalidServiceRouteName' });\n }\n return noop;\n }\n const sanitizedName = parsedRoute.normalizedRouteName;\n\n const routeVersion = params.version;\n const fullName = `sync/${sanitizedName}/${routeVersion}`;\n //? Combined union: both `serverStream` (from `broadcastStream` /\n //? `streamTo`) and `clientStream` (from `_client_v{n}.ts`) flow through\n //? this callback. The type matches `SyncRouteStreamCallbackForFullName`.\n const typedCallback = params.callback as (params: {\n stream: SyncRouteStreamEvent<Prettify<CombinedRouteStream<F, V> extends StreamPayload ? CombinedRouteStream<F, V> : StreamPayload>>;\n }) => void;\n const callback: SyncEventStreamCallback = ({ stream }) => {\n typedCallback({\n stream: stream as SyncRouteStreamEvent<Prettify<CombinedRouteStream<F, V> extends StreamPayload ? CombinedRouteStream<F, V> : StreamPayload>>,\n });\n };\n const callbacks = getStreamCallbacksForRoute(fullName);\n\n const previousForRoute = localStreamRegistryRef.current.get(fullName);\n if (previousForRoute) {\n syncStreamEvents[fullName] = callbacks.filter((cb) => cb !== previousForRoute);\n }\n\n const nextCallbacks = getStreamCallbacksForRoute(fullName);\n if (nextCallbacks.includes(callback)) {\n if (shouldLogDev()) {\n getLogger().warn(`[SyncEvents] Duplicate stream callback registration was ignored`, { fullName });\n }\n\n localStreamRegistryRef.current.set(fullName, callback);\n\n return () => {\n const current = getStreamCallbacksForRoute(fullName);\n syncStreamEvents[fullName] = current.filter((cb) => cb !== callback);\n\n if (localStreamRegistryRef.current.get(fullName) === callback) {\n localStreamRegistryRef.current.delete(fullName);\n }\n };\n }\n\n nextCallbacks.push(callback);\n syncStreamEvents[fullName] = nextCallbacks;\n localStreamRegistryRef.current.set(fullName, callback);\n\n return () => {\n const current = getStreamCallbacksForRoute(fullName);\n syncStreamEvents[fullName] = current.filter((cb) => cb !== callback);\n\n if (localStreamRegistryRef.current.get(fullName) === callback) {\n localStreamRegistryRef.current.delete(fullName);\n }\n };\n }, []);\n\n useEffect(() => {\n const localRegistry = localRegistryRef.current;\n const localStreamRegistry = localStreamRegistryRef.current;\n\n return () => {\n for (const [fullName, callback] of localRegistry.entries()) {\n const current = getCallbacksForRoute(fullName);\n syncEvents[fullName] = current.filter((cb) => cb !== callback);\n }\n\n for (const [fullName, callback] of localStreamRegistry.entries()) {\n const current = getStreamCallbacksForRoute(fullName);\n syncStreamEvents[fullName] = current.filter((cb) => cb !== callback);\n }\n\n localRegistry.clear();\n localStreamRegistry.clear();\n };\n }, []);\n\n return { upsertSyncEventCallback, upsertSyncEventStreamCallback };\n}\n\nexport const useSyncEventTrigger = () => {\n const triggerSyncEvent = useCallback((name: string, clientOutput: unknown = {}, serverOutput: unknown = {}) => {\n triggerSyncCallbacks(name, clientOutput, serverOutput);\n }, []);\n\n const triggerSyncStreamEvent = useCallback((name: string, stream: SyncRouteStreamEvent) => {\n triggerSyncStreamCallbacks(name, stream);\n }, []);\n\n return { triggerSyncEvent, triggerSyncStreamEvent }\n}\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// Sync Receive Bridge — attachSyncReceiver\n// ═══════════════════════════════════════════════════════════════════════════════\n\n//? Shape of the `sync` socket event the server emits to clients. Mirrors the\n//? server's emit payload; extra keys are stream chunk fields.\ninterface SyncEventPayload {\n cb?: string;\n clientOutput?: unknown;\n serverOutput?: unknown;\n message?: string;\n status?: 'success' | 'error' | 'stream';\n fullName?: string;\n errorCode?: string;\n errorParams?: { key: string; value: string | number | boolean }[];\n httpStatus?: number;\n [key: string]: unknown;\n}\n\nconst normalizeSyncRouteKey = (value: string): string => {\n const sanitized = value.replaceAll(/^\\/+|\\/+$/g, '');\n if (sanitized.length === 0) return '';\n if (sanitized.startsWith('sync/')) return sanitized;\n return `sync/${sanitized}`;\n};\n\nconst getSyncRouteKeys = ({ fullName, cb }: { fullName?: string; cb?: string }): string[] => {\n const keys = new Set<string>();\n if (typeof fullName === 'string') {\n const normalized = normalizeSyncRouteKey(fullName);\n if (normalized) keys.add(normalized);\n }\n if (typeof cb === 'string') {\n const normalized = normalizeSyncRouteKey(cb);\n if (normalized) keys.add(normalized);\n }\n return [...keys];\n};\n\n//? Per-socket idempotency guard — attaching twice to the same socket would\n//? double-dispatch every sync event.\nconst attachedSyncSockets = new WeakSet<Socket>();\n\n//? Wire the incoming `sync` socket listener that routes server fan-out into the\n//? registered `upsertSyncEventCallback` / `upsertSyncEventStreamCallback`\n//? subscribers. Previously this logic was inlined in the consumer's\n//? `socketInitializer.ts`; relocating it here makes `@luckystack/sync` own its\n//? own receive path, so a consumer only has to dynamic-import this and call it\n//? (the add-later sync wiring is then a single line, not a copy-paste block).\nexport const attachSyncReceiver = (socketInstance: Socket): void => {\n if (attachedSyncSockets.has(socketInstance)) return;\n attachedSyncSockets.add(socketInstance);\n\n socketInstance.on(socketEventNames.sync, (payload: SyncEventPayload) => {\n const { cb, clientOutput, serverOutput, message, status, fullName, errorCode, errorParams } = payload;\n if (shouldLogDev()) {\n getLogger().debug('Server Sync Response', { payload });\n }\n\n const routeKeys = getSyncRouteKeys({ fullName, cb });\n\n if (status === 'stream') {\n if (routeKeys.length === 0) return;\n\n //? SYNC-N7 — strip all reserved envelope keys from the forwarded stream\n //? payload so a consumer handler that accidentally emits `errorCode`,\n //? `errorParams`, `httpStatus`, or `message` in a stream chunk doesn't\n //? pollute the client callback's stream argument with error-envelope fields.\n //? `status`, `fullName`, `cb` were already stripped; the others are added here.\n const streamPayload = { ...payload };\n delete streamPayload.status;\n delete streamPayload.fullName;\n delete streamPayload.cb;\n delete streamPayload.errorCode;\n delete streamPayload.errorParams;\n delete streamPayload.httpStatus;\n delete streamPayload.message;\n\n if (shouldLogStream()) {\n getLogger().debug('Server Sync Stream', { routeKeys, streamPayload });\n }\n\n for (const routeKey of routeKeys) {\n triggerSyncStreamCallbacks(routeKey, streamPayload);\n }\n return;\n }\n\n if (status === 'error') {\n if (errorCode === 'sync.ignore' || message === 'sync.ignore') return;\n if (shouldNotifyDev()) {\n if (errorCode) {\n notify.error({ key: errorCode, params: errorParams });\n } else if (message) {\n notify.error({ key: message });\n }\n }\n return;\n }\n\n if (routeKeys.length === 0) {\n const errorMessage = `Sync response is missing fullName for cb '${cb ?? 'unknown'}'.`;\n if (shouldLogDev()) {\n getLogger().error(errorMessage);\n }\n if (shouldNotifyDev()) {\n notify.error({ key: 'sync.invalidRequestFormat' });\n }\n //? SYNC-10 — degrade to a logged warning instead of throwing. This runs\n //? inside the `socket.on(sync)` listener; a `throw` here is an uncaught\n //? exception on a server-controlled (malformed) frame that can kill other\n //? listeners bound to the same emit. The error is already logged + notified\n //? above, so `return` loses no information.\n return;\n }\n\n for (const routeKey of routeKeys) {\n triggerSyncCallbacks(routeKey, clientOutput, serverOutput);\n }\n });\n};\n\ntype SocketStatusSetter = Dispatch<\n SetStateAction<{\n self: statusContent;\n [userId: string]: statusContent;\n }>\n>;\n\nconst buildConnectHandler = ({ setSocketStatus }: { setSocketStatus: SocketStatusSetter }) => () => {\n if (shouldLogSocketStatus()) getLogger().info(\"Connected to server\");\n setSocketStatus(prev => ({ ...prev, self: { ...prev.self, status: \"CONNECTED\" } }));\n};\n\nconst buildDisconnectHandler = ({ setSocketStatus }: { setSocketStatus: SocketStatusSetter }) => () => {\n setSocketStatus(prev => ({ ...prev, self: { ...prev.self, status: \"DISCONNECTED\" } }));\n if (shouldLogSocketStatus()) getLogger().info(\"Disconnected, trying to reconnect...\");\n};\n\nconst buildReconnectAttemptHandler = ({ setSocketStatus }: { setSocketStatus: SocketStatusSetter }) => (attempt: number) => {\n setSocketStatus(prev => ({ ...prev, self: { ...prev.self, status: \"RECONNECTING\", reconnectAttempt: attempt } }));\n if (shouldLogSocketStatus()) getLogger().info(`Reconnecting attempt ${String(attempt)}...`);\n};\n\nconst buildUserAfkHandler = ({\n setSocketStatus, sessionRef,\n}: {\n setSocketStatus: SocketStatusSetter;\n sessionRef: RefObject<SessionLayout | null>;\n}) => ({ userId, endTime }: { userId: string; endTime?: number }) => {\n if (sessionRef.current !== null && userId === sessionRef.current.id) {\n setSocketStatus(prev => ({\n ...prev,\n self: { status: \"DISCONNECTED\", reconnectAttempt: undefined, endTime },\n }));\n } else {\n setSocketStatus(prev => ({\n ...prev,\n [userId]: { status: \"DISCONNECTED\", endTime },\n }));\n }\n};\n\nconst buildUserBackHandler = ({ setSocketStatus }: { setSocketStatus: SocketStatusSetter }) => ({ userId }: { userId: string }) => {\n if (shouldLogSocketStatus()) getLogger().debug(\"userBack\", { userId });\n setSocketStatus(prev => ({\n ...prev,\n [userId]: { status: \"CONNECTED\", endTime: undefined },\n }));\n};\n\nconst buildConnectErrorHandler = ({ setSocketStatus }: { setSocketStatus: SocketStatusSetter }) => (err: { message: string }) => {\n if (shouldLogSocketStatus()) getLogger().debug(\"connect_error\", { err });\n setSocketStatus(prev => ({\n ...prev,\n self: { ...prev.self, status: \"DISCONNECTED\", reconnectAttempt: undefined },\n }));\n if (shouldLogDev()) getLogger().error(`Connection error`, err);\n if (shouldNotifyDev()) notify.error({ key: 'common.connectionError' });\n};\n\n//? SYNC-N6 — `initSyncRequest` owns the canonical lifecycle listeners\n//? (`connect`, `disconnect`, `reconnect_attempt`, `userAfk`, `userBack`,\n//? `connect_error`). A consumer's `socketInitializer.ts` that also registers\n//? `disconnect`/`reconnect_attempt` on the same socket instance creates\n//? duplicate handlers — both fire on every event. Since `initSyncRequest` is\n//? idempotent (tears down the previous set before re-binding), consumer code\n//? should delegate lifecycle status updates here and avoid re-registering\n//? those events directly. Duplicate `console.log` in the consumer is harmless\n//? but produces double log lines.\nexport const initSyncRequest = async ({\n setSocketStatus,\n sessionRef\n}: {\n setSocketStatus: SocketStatusSetter;\n sessionRef: RefObject<SessionLayout | null> | null;\n}) => {\n\n if (!await waitForSocket()) { return; }\n if (!socket) { return; }\n if (!sessionRef) { return; }\n\n if (activeLifecycleHandlers) {\n socket.off(socketEventNames.connect, activeLifecycleHandlers.connect);\n socket.off(socketEventNames.disconnect, activeLifecycleHandlers.disconnect);\n socket.off(socketEventNames.reconnectAttempt, activeLifecycleHandlers.reconnectAttempt);\n socket.off(socketEventNames.userAfk, activeLifecycleHandlers.userAfk);\n socket.off(socketEventNames.userBack, activeLifecycleHandlers.userBack);\n socket.off(socketEventNames.connectError, activeLifecycleHandlers.connectError);\n }\n\n const connect = buildConnectHandler({ setSocketStatus });\n const disconnect = buildDisconnectHandler({ setSocketStatus });\n const reconnectAttempt = buildReconnectAttemptHandler({ setSocketStatus });\n const userAfk = buildUserAfkHandler({ setSocketStatus, sessionRef });\n const userBack = buildUserBackHandler({ setSocketStatus });\n const connectError = buildConnectErrorHandler({ setSocketStatus });\n\n activeLifecycleHandlers = {\n connect,\n disconnect,\n reconnectAttempt,\n userAfk,\n userBack,\n connectError,\n };\n\n socket.on(socketEventNames.connect, connect);\n socket.on(socketEventNames.disconnect, disconnect);\n socket.on(socketEventNames.reconnectAttempt, reconnectAttempt);\n socket.on(socketEventNames.userAfk, userAfk);\n socket.on(socketEventNames.userBack, userBack);\n socket.on(socketEventNames.connectError, connectError);\n\n}"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  getRuntimeSyncMaps,
7
7
  validateRequest,
8
8
  extractTokenFromSocket,
9
- formatRoomName,
9
+ formatRoomName as formatRoomName2,
10
10
  getIoInstance as getIoInstance2,
11
11
  tryCatch as tryCatch2,
12
12
  parseTransportRouteName,
@@ -25,12 +25,13 @@ import {
25
25
  setCurrentErrorTrackerIdentity,
26
26
  registerSyncAbortController,
27
27
  unregisterSyncAbortController,
28
- deriveTokenBucketId
28
+ resolveEnvKey
29
29
  } from "@luckystack/core";
30
30
 
31
31
  // src/_shared/streamEmitters.ts
32
32
  import {
33
33
  dispatchHook,
34
+ formatRoomName,
34
35
  getIoInstance,
35
36
  getLogger,
36
37
  getProjectConfig as getProjectConfig2,
@@ -100,12 +101,14 @@ var collectRoomSocketsForPressure = (receiver) => {
100
101
  var buildSyncStreamEmitters = ({
101
102
  cb,
102
103
  receiver,
104
+ userId,
103
105
  resolvedName,
104
106
  emitOriginatorChunk,
105
107
  logLabel,
106
108
  signal,
107
109
  originatorSocket
108
110
  }) => {
111
+ const physicalReceiver = !receiver || receiver === "all" ? receiver : formatRoomName(receiver, { purpose: "broadcast", userId: userId ?? null });
109
112
  const chunkCounters = /* @__PURE__ */ new Map();
110
113
  const bumpChunkIndex = (recipient) => {
111
114
  const next = (chunkCounters.get(recipient) ?? 0) + 1;
@@ -152,7 +155,7 @@ var buildSyncStreamEmitters = ({
152
155
  const io = getIoInstance();
153
156
  if (!io) return;
154
157
  dispatchStreamHooks(receiver, payload);
155
- io.to(receiver).emit(socketEventNames.sync, buildBroadcastFrame(payload));
158
+ io.to(physicalReceiver).emit(socketEventNames.sync, buildBroadcastFrame(payload));
156
159
  };
157
160
  const emitStreamToTokens = (tokens, payload = {}) => {
158
161
  if (isAborted()) {
@@ -180,7 +183,7 @@ var buildSyncStreamEmitters = ({
180
183
  const packetThreshold = Math.max(1, Math.ceil(effectiveThresholdBytes / AVG_PACKET_BYTES));
181
184
  const targets = [];
182
185
  if (originatorSocket) targets.push(originatorSocket);
183
- for (const sock of collectRoomSocketsForPressure(receiver)) {
186
+ for (const sock of collectRoomSocketsForPressure(physicalReceiver)) {
184
187
  if (sock !== originatorSocket) targets.push(sock);
185
188
  }
186
189
  if (targets.length === 0) return;
@@ -278,7 +281,7 @@ var processClientSyncForRecipient = async ({
278
281
  sourceUserId,
279
282
  //? Redact the raw bearer session token before it lands in the
280
283
  //? error-tracker context (`captureException`) — a verbatim token there is
281
- //? a usable credential (defeats HttpOnly-cookie mode). The 8-char prefix
284
+ //? a usable credential (defeats HttpOnly-cookie mode). The 4-char prefix
282
285
  //? still correlates error events.
283
286
  targetToken: redactToken(tempToken),
284
287
  receiver,
@@ -362,7 +365,6 @@ var authorizeSyncReceiver = ({
362
365
  // src/handleSyncRequest.ts
363
366
  var applySyncRateLimits = async ({
364
367
  resolvedName,
365
- token,
366
368
  socket,
367
369
  user,
368
370
  responseIndex,
@@ -381,8 +383,8 @@ var applySyncRateLimits = async ({
381
383
  if (defaultApiLimit !== false && defaultApiLimit > 0) {
382
384
  const identityCb = config.rateLimiting.identity;
383
385
  const customIdentity = identityCb?.({ routeName: resolvedName, userId: user?.id ?? null, ip: resolvedIp, transport: "socket" }) ?? null;
384
- const requesterIdentity = customIdentity?.id ?? (token ? deriveTokenBucketId(token) : resolvedIp);
385
- const keyPrefix = customIdentity?.scope ?? (token ? "token" : "ip");
386
+ const requesterIdentity = customIdentity?.id ?? user?.id ?? resolvedIp;
387
+ const keyPrefix = customIdentity?.scope ?? (user?.id ? "user" : "ip");
386
388
  const rateLimitKey = `${keyPrefix}:${requesterIdentity}:sync:${resolvedName}`;
387
389
  const { allowed, resetIn } = await checkRateLimit({
388
390
  key: rateLimitKey,
@@ -394,14 +396,14 @@ var applySyncRateLimits = async ({
394
396
  //? Parity with the API handler: an anonymous per-route bucket is IP-keyed,
395
397
  //? so the scope is `ip` (with `route` still set to mark it a per-route
396
398
  //? bucket vs the global `:sync:all` IP bucket), never `route`.
397
- scope: token ? "user" : "ip",
399
+ scope: user?.id ? "user" : "ip",
398
400
  key: rateLimitKey,
399
401
  limit: defaultApiLimit,
400
402
  windowMs: config.rateLimiting.windowMs,
401
403
  count: defaultApiLimit + 1,
402
404
  route: resolvedName,
403
405
  userId: user?.id,
404
- ip: token ? void 0 : resolvedIp
406
+ ip: user?.id ? void 0 : resolvedIp
405
407
  });
406
408
  if (typeof responseIndex === "number") {
407
409
  socket.emit(buildSyncResponseEventName(responseIndex), buildSyncError({
@@ -419,7 +421,7 @@ var applySyncRateLimits = async ({
419
421
  }
420
422
  }
421
423
  const defaultIpLimit = config.rateLimiting.defaultIpLimit;
422
- const requesterIsLoopback = config.rateLimiting.skipLoopbackInDev && process.env.NODE_ENV !== "production" && isLoopbackIp(resolvedIp);
424
+ const requesterIsLoopback = config.rateLimiting.skipLoopbackInDev && resolveEnvKey() !== "production" && isLoopbackIp(resolvedIp);
423
425
  if (!requesterIsLoopback && defaultIpLimit !== false && defaultIpLimit > 0) {
424
426
  const requesterIp = resolvedIp;
425
427
  const ipKey = `ip:${requesterIp}:sync:all`;
@@ -475,7 +477,7 @@ function validateSyncMessage({
475
477
  socket,
476
478
  preferredLocale
477
479
  }) {
478
- if (typeof msg != "object") {
480
+ if (typeof msg !== "object" || msg === null || Array.isArray(msg)) {
479
481
  if (shouldLogDev()) {
480
482
  getLogger3().warn("sync: socket message was not a json object");
481
483
  }
@@ -662,7 +664,15 @@ function runReceiverAuth({
662
664
  receiver,
663
665
  allowClientReceiverAll: syncConfig.allowClientReceiverAll,
664
666
  requireRoomMembership: syncConfig.requireRoomMembership,
665
- isMember: () => socket.rooms.has(receiver)
667
+ //? Sockets physically JOIN the FORMATTED room (loadSocket runs the room name
668
+ //? through formatRoomName at join time), so membership must be tested against
669
+ //? the SAME physical name the fanout fetch uses (see physicalReceiver below) —
670
+ //? NOT the raw client `receiver`. Otherwise a non-identity
671
+ //? registerRoomNameFormatter (multi-tenant) makes every joined member fail the
672
+ //? check on the socket transport while HTTP still works.
673
+ isMember: () => socket.rooms.has(
674
+ receiver === "all" ? "all" : formatRoomName2(receiver, { purpose: "broadcast", userId: user?.id ?? null })
675
+ )
666
676
  });
667
677
  if (!receiverAuth.allowed) {
668
678
  if (shouldLogDev()) {
@@ -897,11 +907,10 @@ async function runSyncFanout({
897
907
  cleanupRequest,
898
908
  ignoreSelf,
899
909
  token,
900
- cb,
901
910
  syncObject,
902
911
  functionsObject
903
912
  }) {
904
- const physicalReceiver = receiver === "all" ? "all" : formatRoomName(receiver, { purpose: "broadcast", userId: user?.id ?? null });
913
+ const physicalReceiver = receiver === "all" ? "all" : formatRoomName2(receiver, { purpose: "broadcast", userId: user?.id ?? null });
905
914
  const sockets = physicalReceiver === "all" ? await ioInstance.fetchSockets() : await ioInstance.in(physicalReceiver).fetchSockets();
906
915
  if (sockets.length === 0) {
907
916
  if (shouldLogDev()) {
@@ -982,7 +991,13 @@ async function runSyncFanout({
982
991
  serverOutput: effectiveServerOutput,
983
992
  receiver,
984
993
  resolvedName,
985
- callbackKey: cb,
994
+ //? Server-authoritative callback key (SYNC security): route recipient
995
+ //? callbacks off the SERVER-resolved route, never the client-supplied
996
+ //? `cb`. Otherwise a room co-member could send a legit `name` but a `cb`
997
+ //? for a DIFFERENT route and fire other clients' handlers for that route
998
+ //? (cross-route callback spoofing). The client `cb` stays only for the
999
+ //? originator's own cancel handshake.
1000
+ callbackKey: resolvedName,
986
1001
  transport: "socket",
987
1002
  handlerName: "handleSyncRequest",
988
1003
  logLabel: "sync",
@@ -996,7 +1011,10 @@ async function runSyncFanout({
996
1011
  });
997
1012
  } else {
998
1013
  const result = {
999
- cb,
1014
+ //? Server-authoritative cb (SYNC security): emit the SERVER-resolved
1015
+ //? route so a spoofed client `cb` can't route this payload to a different
1016
+ //? route's callbacks on recipients. Mirrors the _client-path callbackKey.
1017
+ cb: resolvedName,
1000
1018
  fullName: resolvedName,
1001
1019
  serverOutput: effectiveServerOutput,
1002
1020
  clientOutput: {},
@@ -1076,6 +1094,7 @@ async function handleSyncRequestInner({ msg, socket, token }) {
1076
1094
  const { emitServerSyncStream, emitBroadcastSyncStream, emitStreamToTokens, flushPressure } = buildSyncStreamEmitters({
1077
1095
  cb,
1078
1096
  receiver,
1097
+ userId: user?.id ?? null,
1079
1098
  resolvedName,
1080
1099
  logLabel: "sync",
1081
1100
  signal: abortController.signal,
@@ -1174,7 +1193,7 @@ import {
1174
1193
  getRuntimeSyncMaps as getRuntimeSyncMapsFromSource,
1175
1194
  validateRequest as validateRequest2,
1176
1195
  extractTokenFromSocket as extractTokenFromSocket2,
1177
- formatRoomName as formatRoomName2,
1196
+ formatRoomName as formatRoomName3,
1178
1197
  getIoInstance as getIoInstance3,
1179
1198
  tryCatch as tryCatch3,
1180
1199
  parseTransportRouteName as parseTransportRouteName2,
@@ -1185,13 +1204,12 @@ import {
1185
1204
  getLogger as getLogger4,
1186
1205
  runWithErrorTrackerIdentityScope as runWithErrorTrackerIdentityScope2,
1187
1206
  setCurrentErrorTrackerIdentity as setCurrentErrorTrackerIdentity2,
1188
- deriveTokenBucketId as deriveTokenBucketId2,
1189
1207
  isLoopbackIp as isLoopbackIp2,
1190
- extractLanguageFromHeader as extractLanguageFromHeader2
1208
+ extractLanguageFromHeader as extractLanguageFromHeader2,
1209
+ resolveEnvKey as resolveEnvKey2
1191
1210
  } from "@luckystack/core";
1192
1211
  var applyHttpSyncRateLimits = async ({
1193
1212
  resolvedName,
1194
- token,
1195
1213
  requesterIp,
1196
1214
  user,
1197
1215
  buildSyncError,
@@ -1203,8 +1221,8 @@ var applyHttpSyncRateLimits = async ({
1203
1221
  if (effectiveSyncLimit !== false && effectiveSyncLimit > 0) {
1204
1222
  const identityCb = config.rateLimiting.identity;
1205
1223
  const customIdentity = identityCb?.({ routeName: resolvedName, userId: user?.id ?? null, ip: requesterIp ?? "", transport: "http" }) ?? null;
1206
- const requesterIdentity = customIdentity?.id ?? (token ? deriveTokenBucketId2(token) : requesterIp ?? "unknown");
1207
- const keyPrefix = customIdentity?.scope ?? (token ? "token" : "ip");
1224
+ const requesterIdentity = customIdentity?.id ?? user?.id ?? (requesterIp ?? "unknown");
1225
+ const keyPrefix = customIdentity?.scope ?? (user?.id ? "user" : "ip");
1208
1226
  const rateLimitKey = `${keyPrefix}:${requesterIdentity}:sync:${resolvedName}`;
1209
1227
  const { allowed, resetIn } = await checkRateLimit2({
1210
1228
  key: rateLimitKey,
@@ -1216,14 +1234,14 @@ var applyHttpSyncRateLimits = async ({
1216
1234
  //? Parity with the API handler: an anonymous per-route bucket is IP-keyed,
1217
1235
  //? so the scope is `ip` (with `route` still set to mark it a per-route
1218
1236
  //? bucket vs the global `:sync:all` IP bucket), never `route`.
1219
- scope: token ? "user" : "ip",
1237
+ scope: user?.id ? "user" : "ip",
1220
1238
  key: rateLimitKey,
1221
1239
  limit: effectiveSyncLimit,
1222
1240
  windowMs: config.rateLimiting.windowMs,
1223
1241
  count: effectiveSyncLimit + 1,
1224
1242
  route: resolvedName,
1225
1243
  userId: user?.id,
1226
- ip: token ? void 0 : requesterIp
1244
+ ip: user?.id ? void 0 : requesterIp
1227
1245
  });
1228
1246
  return buildSyncError({
1229
1247
  response: {
@@ -1238,7 +1256,7 @@ var applyHttpSyncRateLimits = async ({
1238
1256
  }
1239
1257
  }
1240
1258
  const defaultIpLimit = config.rateLimiting.defaultIpLimit;
1241
- const requesterIsLoopback = config.rateLimiting.skipLoopbackInDev && process.env.NODE_ENV !== "production" && isLoopbackIp2(requesterIp ?? "");
1259
+ const requesterIsLoopback = config.rateLimiting.skipLoopbackInDev && resolveEnvKey2() !== "production" && isLoopbackIp2(requesterIp ?? "");
1242
1260
  if (!requesterIsLoopback && defaultIpLimit !== false && defaultIpLimit > 0) {
1243
1261
  const ipBucket = requesterIp ?? "unknown";
1244
1262
  const ipKey = `ip:${ipBucket}:sync:all`;
@@ -1271,7 +1289,7 @@ var applyHttpSyncRateLimits = async ({
1271
1289
  return null;
1272
1290
  };
1273
1291
  async function stageResolveRoute(ctx) {
1274
- const { name, cb, normalizedReceiver, user, buildSyncError, preferredLocale } = ctx;
1292
+ const { name, cb, data, normalizedReceiver, user, buildSyncError, preferredLocale } = ctx;
1275
1293
  if (!name || typeof name !== "string") {
1276
1294
  return {
1277
1295
  error: buildSyncError({
@@ -1281,6 +1299,15 @@ async function stageResolveRoute(ctx) {
1281
1299
  })
1282
1300
  };
1283
1301
  }
1302
+ if (data === null || typeof data !== "object" || Array.isArray(data)) {
1303
+ return {
1304
+ error: buildSyncError({
1305
+ response: { status: "error", errorCode: "sync.invalidRequest" },
1306
+ preferred: preferredLocale,
1307
+ userLanguage: user?.language
1308
+ })
1309
+ };
1310
+ }
1284
1311
  const parsedRoute = parseTransportRouteName2({ value: name, prefix: "sync" });
1285
1312
  if (parsedRoute.status === "error") {
1286
1313
  return {
@@ -1477,6 +1504,7 @@ async function stageExecuteServer(resolvedName, serverSyncEntry, functionsObject
1477
1504
  const { emitServerSyncStream, emitBroadcastSyncStream, emitStreamToTokens, flushPressure } = buildSyncStreamEmitters({
1478
1505
  cb,
1479
1506
  receiver: normalizedReceiver,
1507
+ userId: user?.id ?? null,
1480
1508
  resolvedName,
1481
1509
  logLabel: "http sync",
1482
1510
  signal: effectiveAbortSignal,
@@ -1595,10 +1623,17 @@ async function stageFanout(resolvedName, callbackName, serverOutput, syncObject,
1595
1623
  userLanguage: user?.language
1596
1624
  });
1597
1625
  }
1598
- const physicalReceiver = normalizedReceiver === "all" ? "all" : formatRoomName2(normalizedReceiver, { purpose: "broadcast", userId: user?.id ?? null });
1626
+ const physicalReceiver = normalizedReceiver === "all" ? "all" : formatRoomName3(normalizedReceiver, { purpose: "broadcast", userId: user?.id ?? null });
1599
1627
  const sockets = physicalReceiver === "all" ? await ioInstance.fetchSockets() : await ioInstance.in(physicalReceiver).fetchSockets();
1600
1628
  let recipientCount = 0;
1629
+ const { fanoutYieldEvery, fanoutYieldMs } = getProjectConfig4().sync;
1630
+ const yieldEvery = Math.max(1, fanoutYieldEvery);
1631
+ let fanoutIterCount = 1;
1601
1632
  for (const tempSocket of sockets) {
1633
+ fanoutIterCount++;
1634
+ if (fanoutIterCount % yieldEvery === 0) {
1635
+ await new Promise((resolve) => setTimeout(resolve, fanoutYieldMs));
1636
+ }
1602
1637
  const tempToken = extractTokenFromSocket2(tempSocket);
1603
1638
  if (ignoreSelf && typeof ignoreSelf === "boolean" && token && token === tempToken) {
1604
1639
  continue;