{"version":3,"sources":["/home/runner/work/liveblocks/liveblocks/packages/liveblocks-zustand/dist/index.cjs","../src/index.ts","../src/version.ts"],"names":[],"mappings":"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","file":"/home/runner/work/liveblocks/liveblocks/packages/liveblocks-zustand/dist/index.cjs","sourcesContent":[null,"import type {\n  BaseUserMeta,\n  Json,\n  JsonObject,\n  LiveObject,\n  LsonObject,\n  Room,\n  Status,\n  User,\n} from \"@liveblocks/client\";\nimport type {\n  BaseMetadata,\n  DE,\n  DM,\n  DP,\n  DS,\n  DU,\n  OpaqueClient,\n  OpaqueRoom,\n  StorageUpdate,\n} from \"@liveblocks/core\";\nimport {\n  detectDupes,\n  errorIf,\n  legacy_patchImmutableObject,\n  lsonToJson,\n  patchLiveObjectKey,\n} from \"@liveblocks/core\";\nimport type { StateCreator, StoreMutatorIdentifier } from \"zustand\";\n\nimport { PKG_FORMAT, PKG_NAME, PKG_VERSION } from \"./version\";\n\ndetectDupes(PKG_NAME, PKG_VERSION, PKG_FORMAT);\n\nconst ERROR_PREFIX = \"Invalid @liveblocks/zustand middleware config.\";\n\nfunction mappingToFunctionIsNotAllowed(key: string): Error {\n  return new Error(\n    `${ERROR_PREFIX} mapping.${key} is invalid. Mapping to a function is not allowed.`\n  );\n}\n\nexport type LiveblocksContext<\n  P extends JsonObject,\n  S extends LsonObject,\n  U extends BaseUserMeta,\n  E extends Json,\n  M extends BaseMetadata,\n> = {\n  /**\n   * Enters a room and starts sync it with zustand state\n   * @param roomId The id of the room\n   */\n  readonly enterRoom: (roomId: string) => () => void;\n  /**\n   * Leaves the currently entered room and stops sync it with zustand state, if\n   * any. If enterRoom was not called before, this is a no-op.\n   */\n  readonly leaveRoom: () => void;\n  /**\n   * The room currently synced to your zustand state.\n   */\n  readonly room: Room<P, S, U, E, M> | null;\n  /**\n   * Other users in the room. Empty no room is currently synced\n   */\n  readonly others: readonly User<P, U>[];\n  /**\n   * Whether or not the room storage is currently loading\n   */\n  readonly isStorageLoading: boolean;\n  /**\n   * Connection status of the room.\n   */\n  readonly status: Status;\n};\n\n/**\n * Adds the `liveblocks` property to your custom Zustand state.\n */\nexport type WithLiveblocks<\n  TState,\n  P extends JsonObject = DP,\n  S extends LsonObject = DS,\n  U extends BaseUserMeta = DU,\n  E extends Json = DE,\n  M extends BaseMetadata = DM,\n> = TState & {\n  readonly liveblocks: LiveblocksContext<P, S, U, E, M>;\n};\n\nexport type Mapping<T> = {\n  [K in keyof T]?: boolean;\n};\n\ntype Options<T> = {\n  /**\n   * Liveblocks client created by @liveblocks/client createClient\n   */\n  client: OpaqueClient;\n  /**\n   * Mapping used to synchronize a part of your zustand state with one Liveblocks Room storage.\n   */\n  storageMapping?: Mapping<T>;\n  /**\n   * Mapping used to synchronize a part of your zustand state with one Liveblocks Room presence.\n   */\n  presenceMapping?: Mapping<T>;\n};\n\ntype OuterLiveblocksMiddleware = <\n  TState,\n  Mps extends [StoreMutatorIdentifier, unknown][] = [],\n  Mcs extends [StoreMutatorIdentifier, unknown][] = [],\n>(\n  config: StateCreator<TState, Mps, Mcs, Omit<TState, \"liveblocks\">>,\n  options: Options<Omit<TState, \"liveblocks\">>\n) => StateCreator<TState, Mps, Mcs, TState>;\n\ntype InnerLiveblocksMiddleware = <\n  TState extends {\n    readonly liveblocks: LiveblocksContext<\n      JsonObject,\n      LsonObject,\n      BaseUserMeta,\n      Json,\n      BaseMetadata\n    >;\n  },\n>(\n  config: StateCreator<TState, [], []>,\n  options: Options<TState>\n) => StateCreator<TState, [], []>;\n\ntype ExtractPresence<TRoom extends OpaqueRoom> =\n  TRoom extends Room<infer P, any, any, any, any> ? P : never;\n\ntype ExtractStorage<TRoom extends OpaqueRoom> =\n  TRoom extends Room<any, infer S, any, any, any> ? S : never;\n\nconst middlewareImpl: InnerLiveblocksMiddleware = (config, options) => {\n  type TState = ReturnType<typeof config>;\n  type TLiveblocksContext = TState[\"liveblocks\"];\n  type TRoom = NonNullable<TLiveblocksContext[\"room\"]>;\n  type P = ExtractPresence<TRoom>;\n  type S = ExtractStorage<TRoom>;\n\n  const { client, presenceMapping, storageMapping } = validateOptions(options);\n  return (set, get, api) => {\n    let maybeRoom: TRoom | null = null;\n    let isPatching: boolean = false;\n    let storageRoot: LiveObject<S> | null = null;\n    let unsubscribeCallbacks: Array<() => void> = [];\n    let lastRoomId: string | null = null;\n    let lastLeaveFn: (() => void) | null = null;\n\n    function enterRoom(newRoomId: string): void {\n      if (lastRoomId === newRoomId) {\n        return;\n      }\n\n      lastRoomId = newRoomId;\n      if (lastLeaveFn !== null) {\n        // First leave the old room before entering a potential new one\n        lastLeaveFn();\n      }\n\n      const initialPresence = selectFields(\n        get(),\n        presenceMapping\n      ) as unknown as P;\n\n      const { room, leave } = client.enterRoom(newRoomId, {\n        initialPresence,\n      }) as unknown as { room: TRoom; leave: () => void };\n      maybeRoom = room;\n\n      updateLiveblocksContext(set, { isStorageLoading: true, room });\n\n      unsubscribeCallbacks.push(\n        room.events.others.subscribe(({ others }) => {\n          updateLiveblocksContext(set, { others });\n        })\n      );\n\n      unsubscribeCallbacks.push(\n        room.events.status.subscribe((status) => {\n          updateLiveblocksContext(set, {\n            status,\n          });\n        })\n      );\n\n      unsubscribeCallbacks.push(\n        room.events.myPresence.subscribe(() => {\n          if (isPatching === false) {\n            set(\n              selectFields(\n                room.getPresence(),\n                presenceMapping\n              ) as Partial<TState>\n            );\n          }\n        })\n      );\n\n      void room.getStorage().then(({ root }) => {\n        const updates = {} as Partial<TState>;\n\n        room.batch(() => {\n          for (const key in storageMapping) {\n            const liveblocksStatePart = root.get(key);\n            if (liveblocksStatePart === undefined) {\n              updates[key] = get()[key];\n              patchLiveObjectKey(\n                root,\n                key,\n                undefined,\n                get()[key] as Json | undefined\n              );\n            } else {\n              updates[key] = lsonToJson(\n                liveblocksStatePart\n              ) as unknown as TState[Extract<keyof TState, string>];\n            }\n          }\n        });\n\n        set(updates);\n\n        storageRoot = root as LiveObject<S>;\n        unsubscribeCallbacks.push(\n          room.subscribe(\n            root,\n            (updates) => {\n              if (isPatching === false) {\n                set(patchState(get(), updates, storageMapping));\n              }\n            },\n            { isDeep: true }\n          )\n        );\n\n        // set isLoading storage to false once storage is loaded\n        updateLiveblocksContext(set, {\n          isStorageLoading: false,\n        });\n      });\n\n      lastLeaveFn = () => {\n        for (const unsubscribe of unsubscribeCallbacks) {\n          unsubscribe();\n        }\n        unsubscribeCallbacks = [];\n\n        storageRoot = null;\n        maybeRoom = null;\n        isPatching = false;\n\n        lastRoomId = null;\n        lastLeaveFn = null;\n        leave();\n\n        updateLiveblocksContext(set, {\n          others: [],\n          status: \"initial\",\n          isStorageLoading: false,\n          room: null,\n        });\n      };\n    }\n\n    function leaveRoom() {\n      lastLeaveFn?.();\n    }\n\n    const store = config(\n      (...args) => {\n        const { liveblocks: _, ...oldState } = get();\n        // @ts-expect-error a bit too dynamic to type\n        set(...args);\n        const { liveblocks: __, ...newState } = get();\n\n        if (maybeRoom) {\n          const room = maybeRoom;\n          isPatching = true;\n          updatePresence(\n            room,\n            oldState as JsonObject,\n            newState as JsonObject,\n            presenceMapping\n          );\n\n          room.batch(() => {\n            if (storageRoot) {\n              patchLiveblocksStorage(\n                storageRoot,\n                oldState,\n                newState,\n                storageMapping\n              );\n            }\n          });\n\n          isPatching = false;\n        }\n      },\n      get,\n      api\n    );\n\n    return {\n      ...store,\n      liveblocks: {\n        enterRoom,\n        leaveRoom,\n        room: null,\n        others: [],\n        connection: \"closed\",\n        isStorageLoading: false,\n      },\n    };\n  };\n};\n\nexport const liveblocks =\n  middlewareImpl as unknown as OuterLiveblocksMiddleware;\n\nfunction patchState<T>(\n  state: T,\n  updates: StorageUpdate[],\n  mapping: Mapping<T>\n) {\n  const partialState: Partial<T> = {};\n\n  for (const key in mapping) {\n    partialState[key] = state[key];\n  }\n\n  const patched = legacy_patchImmutableObject(\n    partialState as JsonObject,\n    updates\n  );\n\n  const result: Partial<T> = {};\n\n  for (const key in mapping) {\n    // @ts-expect-error key is a key of T\n    result[key] = patched[key];\n  }\n\n  return result;\n}\n\nfunction selectFields<TState>(\n  presence: TState,\n  mapping: Mapping<TState>\n): /* TODO: Actually, Pick<TState, keyof Mapping<TState>> ? */\nPartial<TState> {\n  const partialState = {} as Partial<TState>;\n  for (const key in mapping) {\n    partialState[key] = presence[key];\n  }\n  return partialState;\n}\n\nfunction updateLiveblocksContext<\n  TState,\n  P extends JsonObject,\n  S extends LsonObject,\n  U extends BaseUserMeta,\n  E extends Json,\n  M extends BaseMetadata,\n>(\n  set: (\n    callbackOrPartial: (\n      current: WithLiveblocks<TState, P, S, U, E, M>\n    ) => WithLiveblocks<TState, P, S, U, E> | Partial<any>\n  ) => void,\n  partial: Partial<LiveblocksContext<P, S, U, E, M>>\n) {\n  set((state) => ({ liveblocks: { ...state.liveblocks, ...partial } }));\n}\n\nfunction updatePresence<\n  P extends JsonObject,\n  S extends LsonObject,\n  U extends BaseUserMeta,\n  E extends Json,\n  M extends BaseMetadata,\n>(\n  room: Room<P, S, U, E, M>,\n  oldState: P,\n  newState: P,\n  presenceMapping: Mapping<P>\n) {\n  for (const key in presenceMapping) {\n    if (typeof newState[key] === \"function\") {\n      throw mappingToFunctionIsNotAllowed(key);\n    }\n\n    if (oldState[key] !== newState[key]) {\n      const val = newState?.[key];\n      const patch = {} as Partial<P>;\n      patch[key] = val;\n      room.updatePresence(patch);\n    }\n  }\n}\n\nfunction patchLiveblocksStorage<O extends LsonObject, TState>(\n  root: LiveObject<O>,\n  oldState: TState,\n  newState: TState,\n  mapping: Mapping<TState>\n) {\n  for (const key in mapping) {\n    if (\n      process.env.NODE_ENV !== \"production\" &&\n      typeof newState[key] === \"function\"\n    ) {\n      throw mappingToFunctionIsNotAllowed(key);\n    }\n\n    if (oldState[key] !== newState[key]) {\n      const oldVal = oldState[key] as Json | undefined;\n      const newVal = newState[key] as Json | undefined;\n      patchLiveObjectKey(root, key, oldVal, newVal);\n    }\n  }\n}\n\nfunction isObject(value: unknown): value is object {\n  return Object.prototype.toString.call(value) === \"[object Object]\";\n}\n\nfunction validateNoDuplicateKeys<T>(\n  storageMapping: Mapping<T>,\n  presenceMapping: Mapping<T>\n) {\n  for (const key in storageMapping) {\n    if (presenceMapping[key] !== undefined) {\n      throw new Error(\n        `${ERROR_PREFIX} \"${key}\" is mapped on both presenceMapping and storageMapping. A key shouldn't exist on both mapping.`\n      );\n    }\n  }\n}\n\n/**\n * Remove false keys from mapping and generate to a new object to avoid potential mutation from outside the middleware\n */\nfunction validateMapping<T>(\n  mapping: Mapping<T>,\n  mappingType: \"storageMapping\" | \"presenceMapping\"\n): Mapping<T> {\n  errorIf(\n    !isObject(mapping),\n    `${ERROR_PREFIX} ${mappingType} should be an object where the values are boolean.`\n  );\n\n  const result: Mapping<T> = {};\n  for (const key in mapping) {\n    errorIf(\n      typeof mapping[key] !== \"boolean\",\n      `${ERROR_PREFIX} ${mappingType}.${key} value should be a boolean`\n    );\n\n    if (mapping[key] === true) {\n      result[key] = true;\n    }\n  }\n  return result;\n}\n\nfunction validateOptions<TState>(options: Options<TState>): {\n  client: OpaqueClient;\n  presenceMapping: Mapping<TState>;\n  storageMapping: Mapping<TState>;\n} {\n  const client = options.client;\n  errorIf(!client, `${ERROR_PREFIX} client is missing`);\n\n  const storageMapping = validateMapping(\n    options.storageMapping ?? {},\n    \"storageMapping\"\n  );\n\n  const presenceMapping = validateMapping(\n    options.presenceMapping ?? {},\n    \"presenceMapping\"\n  );\n\n  if (process.env.NODE_ENV !== \"production\") {\n    validateNoDuplicateKeys(storageMapping, presenceMapping);\n  }\n\n  return { client, storageMapping, presenceMapping };\n}\n","declare const __VERSION__: string;\ndeclare const TSUP_FORMAT: string;\n\nexport const PKG_NAME = \"@liveblocks/zustand\";\nexport const PKG_VERSION = typeof __VERSION__ === \"string\" && __VERSION__;\nexport const PKG_FORMAT = typeof TSUP_FORMAT === \"string\" && TSUP_FORMAT;\n"]}