@upstash/react-redis-browser 0.1.2-canary-4 → 0.1.2-canary-6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -2888,7 +2888,13 @@ function Toaster() {
2888
2888
  }
2889
2889
 
2890
2890
  // src/components/databrowser/hooks/use-keys.tsx
2891
- import { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useMemo as useMemo2 } from "react";
2891
+ import {
2892
+ createContext as createContext2,
2893
+ useCallback as useCallback2,
2894
+ useContext as useContext2,
2895
+ useMemo as useMemo2,
2896
+ useRef as useRef2
2897
+ } from "react";
2892
2898
  import { useInfiniteQuery } from "@tanstack/react-query";
2893
2899
 
2894
2900
  // src/components/databrowser/hooks/use-fetch-keys.ts
@@ -2908,35 +2914,28 @@ var DATA_TYPE_NAMES = {
2908
2914
 
2909
2915
  // src/components/databrowser/hooks/use-fetch-keys.ts
2910
2916
  var PAGE_SIZE = 30;
2911
- var INITIAL_FETCH_COUNT = 100;
2912
- var MAX_FETCH_COUNT = 1e3;
2917
+ var FETCH_COUNTS = [100, 200, 400, 800];
2913
2918
  var useFetchKeys = (search) => {
2914
2919
  const { redis } = useDatabrowser();
2915
2920
  const cache = useRef();
2916
2921
  const lastKey = useRef();
2917
- const getPage = useCallback(
2918
- (page) => {
2919
- const newKey = JSON.stringify(search);
2920
- if (!cache.current || lastKey.current !== newKey) {
2921
- cache.current = new PaginationCache(redis, search.key, search.type);
2922
- lastKey.current = newKey;
2923
- }
2924
- return cache.current.getPage(page);
2925
- },
2926
- [search]
2927
- );
2922
+ const fetchKeys = useCallback(() => {
2923
+ const newKey = JSON.stringify(search);
2924
+ if (!cache.current || lastKey.current !== newKey) {
2925
+ cache.current = new PaginationCache(redis, search.key, search.type);
2926
+ lastKey.current = newKey;
2927
+ }
2928
+ return cache.current.fetchNewKeys();
2929
+ }, [search]);
2928
2930
  const resetCache = useCallback(() => {
2929
2931
  cache.current = void 0;
2930
2932
  lastKey.current = void 0;
2931
2933
  }, []);
2932
2934
  return {
2933
- getPage,
2935
+ fetchKeys,
2934
2936
  resetCache
2935
2937
  };
2936
2938
  };
2937
- function slicePage(keys, page) {
2938
- return keys.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE);
2939
- }
2940
2939
  var PaginationCache = class {
2941
2940
  constructor(redis, searchTerm, typeFilter) {
2942
2941
  this.redis = redis;
@@ -2952,8 +2951,9 @@ var PaginationCache = class {
2952
2951
  );
2953
2952
  targetCount = 0;
2954
2953
  isFetching = false;
2955
- async getPage(page) {
2956
- this.targetCount = (page + 1) * PAGE_SIZE + 1;
2954
+ async fetchNewKeys() {
2955
+ const initialKeys = new Set(this.getKeys().map(([key]) => key));
2956
+ this.targetCount = this.getKeys().length + PAGE_SIZE;
2957
2957
  void this.startFetch();
2958
2958
  await new Promise((resolve) => {
2959
2959
  const interval = setInterval(() => {
@@ -2963,10 +2963,9 @@ var PaginationCache = class {
2963
2963
  }
2964
2964
  }, 100);
2965
2965
  });
2966
- const hasEnoughForNextPage = this.getLength() > (page + 1) * PAGE_SIZE;
2967
- const hasNextPage = !this.isAllEnded() || hasEnoughForNextPage;
2966
+ const hasNextPage = !this.isAllEnded();
2968
2967
  return {
2969
- keys: slicePage(this.getKeys(), page),
2968
+ keys: this.getKeys().filter(([key]) => !initialKeys.has(key)),
2970
2969
  hasNextPage
2971
2970
  };
2972
2971
  }
@@ -2992,33 +2991,29 @@ var PaginationCache = class {
2992
2991
  }
2993
2992
  }
2994
2993
  fetchForType = async (type) => {
2995
- let fetchCount = INITIAL_FETCH_COUNT;
2994
+ let i = 0;
2996
2995
  while (true) {
2997
2996
  const cursor = this.cache[type].cursor;
2998
2997
  if (cursor === "-1" || this.getLength() >= this.targetCount) {
2999
2998
  break;
3000
2999
  }
3000
+ const fetchCount = FETCH_COUNTS[Math.min(i, FETCH_COUNTS.length - 1)];
3001
3001
  const [nextCursor, newKeys] = await this.redis.scan(cursor, {
3002
3002
  count: fetchCount,
3003
3003
  match: this.searchTerm,
3004
3004
  type
3005
3005
  });
3006
- fetchCount = Math.min(fetchCount * 2, MAX_FETCH_COUNT);
3007
- const dedupedSet = /* @__PURE__ */ new Set([...this.cache[type].keys, ...newKeys]);
3008
- this.cache[type].keys = [...dedupedSet];
3006
+ this.cache[type].keys = [...this.cache[type].keys, ...newKeys];
3009
3007
  this.cache[type].cursor = nextCursor === "0" ? "-1" : nextCursor;
3008
+ i++;
3010
3009
  }
3011
3010
  };
3012
3011
  async fetch() {
3013
3012
  const types = this.typeFilter ? [this.typeFilter] : DATA_TYPES;
3014
3013
  await Promise.all(types.map(this.fetchForType));
3015
3014
  }
3016
- // TODO: Yusuf, implement this function
3017
3015
  isAllEnded() {
3018
3016
  const types = this.typeFilter ? [this.typeFilter] : DATA_TYPES;
3019
- if (!Array.isArray(types)) {
3020
- throw new TypeError("types is not an array");
3021
- }
3022
3017
  return types.every((type) => this.cache[type] && this.cache[type].cursor === "-1");
3023
3018
  }
3024
3019
  };
@@ -3036,17 +3031,21 @@ var KeysProvider = ({ children }) => {
3036
3031
  }),
3037
3032
  [searchState]
3038
3033
  );
3039
- const { getPage, resetCache } = useFetchKeys(search);
3034
+ const { fetchKeys, resetCache } = useFetchKeys(search);
3035
+ const pageRef = useRef2(0);
3040
3036
  const query = useInfiniteQuery({
3041
3037
  queryKey: [FETCH_KEYS_QUERY_KEY, search],
3042
3038
  initialPageParam: 0,
3043
- queryFn: async ({ pageParam: pageIndex }) => {
3044
- return getPage(pageIndex);
3039
+ queryFn: async ({ pageParam: page }) => {
3040
+ if (pageRef.current > page) resetCache();
3041
+ pageRef.current = page;
3042
+ return await fetchKeys();
3045
3043
  },
3046
3044
  select: (data) => data,
3047
3045
  getNextPageParam: (lastPage, __, lastPageIndex) => {
3048
3046
  return lastPage.hasNextPage ? lastPageIndex + 1 : void 0;
3049
- }
3047
+ },
3048
+ refetchOnMount: false
3050
3049
  });
3051
3050
  const refetch = useCallback2(() => {
3052
3051
  resetCache();
@@ -3054,8 +3053,14 @@ var KeysProvider = ({ children }) => {
3054
3053
  }, [query, resetCache]);
3055
3054
  const keys = useMemo2(() => {
3056
3055
  const keys2 = query.data?.pages.flatMap((page) => page.keys) ?? [];
3057
- const keysSet = new Set(keys2.map(([key, _]) => key));
3058
- return keys2.filter(([key, _]) => keysSet.has(key));
3056
+ const keysSet = /* @__PURE__ */ new Set();
3057
+ const dedupedKeys = [];
3058
+ for (const key of keys2) {
3059
+ if (keysSet.has(key[0])) continue;
3060
+ keysSet.add(key[0]);
3061
+ dedupedKeys.push(key);
3062
+ }
3063
+ return dedupedKeys;
3059
3064
  }, [query.data]);
3060
3065
  return /* @__PURE__ */ jsx4(
3061
3066
  KeysContext.Provider,
@@ -3320,7 +3325,7 @@ var useFetchListItems = ({ dataKey, type }) => {
3320
3325
  count: LIST_DISPLAY_PAGE_SIZE
3321
3326
  });
3322
3327
  return {
3323
- cursor: nextCursor,
3328
+ cursor: nextCursor === "0" ? void 0 : nextCursor,
3324
3329
  keys: keys.map((key) => ({ key }))
3325
3330
  };
3326
3331
  },
@@ -3336,7 +3341,7 @@ var useFetchListItems = ({ dataKey, type }) => {
3336
3341
  rev: true
3337
3342
  });
3338
3343
  return {
3339
- cursor: lastIndex + LIST_DISPLAY_PAGE_SIZE,
3344
+ cursor: res.length < LIST_DISPLAY_PAGE_SIZE ? void 0 : lastIndex + LIST_DISPLAY_PAGE_SIZE,
3340
3345
  keys: transformArray(res)
3341
3346
  };
3342
3347
  },
@@ -3351,7 +3356,7 @@ var useFetchListItems = ({ dataKey, type }) => {
3351
3356
  count: LIST_DISPLAY_PAGE_SIZE
3352
3357
  });
3353
3358
  return {
3354
- cursor: res[0],
3359
+ cursor: res[0] === "0" ? void 0 : res[0],
3355
3360
  keys: transformArray(res[1])
3356
3361
  };
3357
3362
  },
@@ -3365,7 +3370,7 @@ var useFetchListItems = ({ dataKey, type }) => {
3365
3370
  const lastIndex = Number(pageParam);
3366
3371
  const values = await redis.lrange(dataKey, lastIndex, lastIndex + LIST_DISPLAY_PAGE_SIZE);
3367
3372
  return {
3368
- cursor: lastIndex + LIST_DISPLAY_PAGE_SIZE,
3373
+ cursor: values.length < LIST_DISPLAY_PAGE_SIZE ? void 0 : lastIndex + LIST_DISPLAY_PAGE_SIZE,
3369
3374
  keys: values.map((value, i) => ({
3370
3375
  key: (lastIndex + i).toString(),
3371
3376
  value
@@ -3377,17 +3382,18 @@ var useFetchListItems = ({ dataKey, type }) => {
3377
3382
  const streamQuery = useInfiniteQuery2({
3378
3383
  enabled: type === "stream",
3379
3384
  queryKey: [FETCH_LIST_ITEMS_QUERY_KEY, dataKey, "stream"],
3380
- initialPageParam: "0",
3385
+ initialPageParam: "-",
3381
3386
  queryFn: async ({ pageParam: lastId }) => {
3382
3387
  const messages = await redis.xrange(
3383
3388
  dataKey,
3384
- lastId,
3389
+ lastId === "-" ? "-" : `(${lastId}`,
3385
3390
  "+",
3386
- LIST_DISPLAY_PAGE_SIZE
3391
+ // +1 since first message is the last one
3392
+ LIST_DISPLAY_PAGE_SIZE + 1
3387
3393
  );
3388
3394
  const lastMessageId = messages.length > 0 ? messages.at(-1)?.[0] : void 0;
3389
3395
  return {
3390
- cursor: lastMessageId,
3396
+ cursor: messages.length < LIST_DISPLAY_PAGE_SIZE ? void 0 : lastMessageId,
3391
3397
  keys: messages.map(([id, fields]) => ({
3392
3398
  key: id,
3393
3399
  value: fields.join("\n")
@@ -3790,30 +3796,30 @@ function DeleteAlertDialog({
3790
3796
  children,
3791
3797
  onDeleteConfirm,
3792
3798
  open,
3793
- onOpenChange
3799
+ onOpenChange,
3800
+ deletionType
3794
3801
  }) {
3795
3802
  return /* @__PURE__ */ jsxs5(AlertDialog, { open, onOpenChange, children: [
3796
3803
  children && /* @__PURE__ */ jsx10(AlertDialogTrigger, { asChild: true, children }),
3797
3804
  /* @__PURE__ */ jsxs5(AlertDialogContent, { children: [
3798
3805
  /* @__PURE__ */ jsxs5(AlertDialogHeader, { children: [
3799
- /* @__PURE__ */ jsx10(AlertDialogTitle, { children: "Irreversible Action!" }),
3806
+ /* @__PURE__ */ jsx10(AlertDialogTitle, { children: deletionType === "item" ? "Delete Item" : "Delete Key" }),
3800
3807
  /* @__PURE__ */ jsxs5(AlertDialogDescription, { className: "mt-5", children: [
3801
- /* @__PURE__ */ jsx10("span", { className: "font-bold", children: "This action CANNOT BE UNDONE." }),
3802
- /* @__PURE__ */ jsx10("br", {}),
3808
+ "Are you sure you want to delete this ",
3809
+ deletionType,
3810
+ "?",
3803
3811
  /* @__PURE__ */ jsx10("br", {}),
3804
- "By proceeding, you will ",
3805
- /* @__PURE__ */ jsx10("span", { className: "font-bold", children: "PERMANENTLY REMOVE" }),
3806
- " your data from our servers, resulting in complete and irreversible loss of your information."
3812
+ "This action cannot be undone."
3807
3813
  ] })
3808
3814
  ] }),
3809
3815
  /* @__PURE__ */ jsxs5(AlertDialogFooter, { children: [
3810
- /* @__PURE__ */ jsx10(AlertDialogCancel, { children: "Cancel" }),
3816
+ /* @__PURE__ */ jsx10(AlertDialogCancel, { type: "button", children: "Cancel" }),
3811
3817
  /* @__PURE__ */ jsx10(
3812
3818
  AlertDialogAction,
3813
3819
  {
3814
3820
  className: "bg-red-500 text-gray-50 hover:bg-red-600",
3815
3821
  onClick: onDeleteConfirm,
3816
- children: "Delete"
3822
+ children: "Yes, Delete"
3817
3823
  }
3818
3824
  )
3819
3825
  ] })
@@ -3826,39 +3832,60 @@ import { Fragment, jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
3826
3832
  var ItemContextMenu = ({
3827
3833
  children,
3828
3834
  dataKey,
3829
- itemKey,
3830
- itemValue,
3831
3835
  type
3832
3836
  }) => {
3833
3837
  const { mutate: editItem } = useEditListItem();
3834
3838
  const [isAlertOpen, setAlertOpen] = useState4(false);
3839
+ const [data, setData] = useState4();
3835
3840
  return /* @__PURE__ */ jsxs6(Fragment, { children: [
3836
3841
  /* @__PURE__ */ jsx11(
3837
3842
  DeleteAlertDialog,
3838
3843
  {
3844
+ deletionType: "item",
3839
3845
  open: isAlertOpen,
3840
3846
  onOpenChange: setAlertOpen,
3841
3847
  onDeleteConfirm: (e) => {
3842
3848
  e.stopPropagation();
3843
- editItem({
3844
- type,
3845
- dataKey,
3846
- itemKey,
3847
- // For deletion
3848
- newKey: void 0
3849
- });
3849
+ if (data) {
3850
+ editItem({
3851
+ type,
3852
+ dataKey,
3853
+ itemKey: data?.key,
3854
+ // For deletion
3855
+ newKey: void 0
3856
+ });
3857
+ }
3850
3858
  setAlertOpen(false);
3851
3859
  }
3852
3860
  }
3853
3861
  ),
3854
3862
  /* @__PURE__ */ jsxs6(ContextMenu, { children: [
3855
- /* @__PURE__ */ jsx11(ContextMenuTrigger, { asChild: true, children }),
3863
+ /* @__PURE__ */ jsx11(
3864
+ ContextMenuTrigger,
3865
+ {
3866
+ asChild: true,
3867
+ onContextMenu: (e) => {
3868
+ const el = e.target;
3869
+ const item = el.closest("[data-item-key]");
3870
+ if (item && item instanceof HTMLElement && item.dataset.itemKey !== void 0) {
3871
+ setData({
3872
+ key: item.dataset.itemKey,
3873
+ value: item.dataset.itemValue
3874
+ });
3875
+ } else {
3876
+ throw new Error("Key not found");
3877
+ }
3878
+ },
3879
+ children
3880
+ }
3881
+ ),
3856
3882
  /* @__PURE__ */ jsxs6(ContextMenuContent, { children: [
3857
3883
  /* @__PURE__ */ jsx11(
3858
3884
  ContextMenuItem,
3859
3885
  {
3860
3886
  onClick: () => {
3861
- navigator.clipboard.writeText(itemKey);
3887
+ if (!data) return;
3888
+ navigator.clipboard.writeText(data?.key);
3862
3889
  toast({
3863
3890
  description: "Key copied to clipboard"
3864
3891
  });
@@ -3866,11 +3893,11 @@ var ItemContextMenu = ({
3866
3893
  children: "Copy key"
3867
3894
  }
3868
3895
  ),
3869
- itemValue !== void 0 && /* @__PURE__ */ jsx11(
3896
+ data?.value && /* @__PURE__ */ jsx11(
3870
3897
  ContextMenuItem,
3871
3898
  {
3872
3899
  onClick: () => {
3873
- navigator.clipboard.writeText(itemValue);
3900
+ navigator.clipboard.writeText(data?.value ?? "");
3874
3901
  toast({
3875
3902
  description: "Value copied to clipboard"
3876
3903
  });
@@ -4540,7 +4567,7 @@ function KeyActions({ dataKey, content }) {
4540
4567
  children: "Copy content"
4541
4568
  }
4542
4569
  ),
4543
- /* @__PURE__ */ jsx22(DeleteAlertDialog, { onDeleteConfirm: async () => await deleteKey(dataKey), children: /* @__PURE__ */ jsx22(DropdownMenuItem, { onSelect: (e) => e.preventDefault(), children: "Delete key" }) })
4570
+ /* @__PURE__ */ jsx22(DeleteAlertDialog, { deletionType: "key", onDeleteConfirm: async () => await deleteKey(dataKey), children: /* @__PURE__ */ jsx22(DropdownMenuItem, { onSelect: (e) => e.preventDefault(), children: "Delete key" }) })
4544
4571
  ] })
4545
4572
  ] });
4546
4573
  }
@@ -4629,7 +4656,7 @@ var ContentTypeSelect = ({
4629
4656
  };
4630
4657
 
4631
4658
  // src/components/databrowser/components/display/input/custom-editor.tsx
4632
- import { useEffect as useEffect6, useRef as useRef2 } from "react";
4659
+ import { useEffect as useEffect6, useRef as useRef3 } from "react";
4633
4660
  import { Editor, useMonaco } from "@monaco-editor/react";
4634
4661
 
4635
4662
  // src/components/databrowser/copy-button.tsx
@@ -4673,11 +4700,11 @@ var CustomEditor = ({
4673
4700
  language,
4674
4701
  value,
4675
4702
  onChange,
4676
- maxDynamicHeight,
4703
+ height,
4677
4704
  showCopyButton
4678
4705
  }) => {
4679
4706
  const monaco = useMonaco();
4680
- const editorRef = useRef2();
4707
+ const editorRef = useRef3();
4681
4708
  useEffect6(() => {
4682
4709
  if (!monaco || !editorRef.current) {
4683
4710
  return;
@@ -4687,9 +4714,9 @@ var CustomEditor = ({
4687
4714
  return /* @__PURE__ */ jsxs18(
4688
4715
  "div",
4689
4716
  {
4690
- className: cn("group/editor relative", maxDynamicHeight === void 0 && "h-full p-2"),
4717
+ className: cn("group/editor relative", height === void 0 && "h-full p-2"),
4691
4718
  style: {
4692
- height: maxDynamicHeight
4719
+ height
4693
4720
  },
4694
4721
  children: [
4695
4722
  /* @__PURE__ */ jsx27(
@@ -4749,7 +4776,7 @@ import { Fragment as Fragment4, jsx as jsx28 } from "react/jsx-runtime";
4749
4776
  var useField = ({
4750
4777
  name,
4751
4778
  form,
4752
- isEditorDynamic = false,
4779
+ height,
4753
4780
  showCopyButton
4754
4781
  }) => {
4755
4782
  const { field, fieldState } = useController({
@@ -4787,7 +4814,7 @@ var useField = ({
4787
4814
  language: contentType === "JSON" ? "json" : "plaintext",
4788
4815
  value: field.value,
4789
4816
  onChange: field.onChange,
4790
- maxDynamicHeight: isEditorDynamic ? 100 : void 0,
4817
+ height,
4791
4818
  showCopyButton
4792
4819
  }
4793
4820
  ) })
@@ -4839,13 +4866,14 @@ var ListEditForm = ({
4839
4866
  });
4840
4867
  return /* @__PURE__ */ jsx29(FormProvider, { ...form, children: /* @__PURE__ */ jsxs19("form", { onSubmit, className: "flex flex-col gap-2", children: [
4841
4868
  /* @__PURE__ */ jsxs19("div", { className: "flex grow flex-col gap-2", children: [
4842
- type !== "list" && /* @__PURE__ */ jsx29(FormItem, { name: "key", label: keyLabel }),
4843
- type === "zset" ? /* @__PURE__ */ jsx29(NumberFormItem, { name: "value", label: valueLabel }) : type !== "set" && /* @__PURE__ */ jsx29(FormItem, { name: "value", label: valueLabel })
4869
+ type !== "list" && /* @__PURE__ */ jsx29(FormItem, { name: "key", height: type === "set" ? 250 : 100, label: keyLabel }),
4870
+ type === "zset" ? /* @__PURE__ */ jsx29(NumberFormItem, { name: "value", label: valueLabel }) : type !== "set" && /* @__PURE__ */ jsx29(FormItem, { name: "value", height: type === "list" ? 250 : 100, label: valueLabel })
4844
4871
  ] }),
4845
4872
  /* @__PURE__ */ jsxs19("div", { className: "flex justify-end gap-2", children: [
4846
4873
  /* @__PURE__ */ jsx29(
4847
4874
  Button,
4848
4875
  {
4876
+ type: "button",
4849
4877
  onClick: () => {
4850
4878
  setSelectedListItem(void 0);
4851
4879
  },
@@ -4889,12 +4917,16 @@ var NumberFormItem = ({ name, label }) => {
4889
4917
  )
4890
4918
  ] });
4891
4919
  };
4892
- var FormItem = ({ name, label }) => {
4920
+ var FormItem = ({
4921
+ name,
4922
+ label,
4923
+ height
4924
+ }) => {
4893
4925
  const form = useFormContext();
4894
4926
  const { editor, selector } = useField({
4895
4927
  name,
4896
4928
  form,
4897
- isEditorDynamic: true,
4929
+ height,
4898
4930
  showCopyButton: true
4899
4931
  });
4900
4932
  return /* @__PURE__ */ jsxs19("div", { className: "flex flex-col gap-1", children: [
@@ -4923,7 +4955,7 @@ var ListDisplay = ({ dataKey, type }) => {
4923
4955
  return /* @__PURE__ */ jsxs20("div", { className: "flex h-full flex-col gap-2", children: [
4924
4956
  /* @__PURE__ */ jsx30(DisplayHeader, { dataKey, type }),
4925
4957
  selectedListItem && /* @__PURE__ */ jsx30(ListEditDisplay, { dataKey, type, item: selectedListItem }),
4926
- /* @__PURE__ */ jsx30("div", { className: cn("min-h-0 grow", selectedListItem && "hidden"), children: /* @__PURE__ */ jsx30(InfiniteScroll, { query, children: /* @__PURE__ */ jsx30("div", { className: "pr-3", children: /* @__PURE__ */ jsx30("table", { className: "w-full", children: /* @__PURE__ */ jsx30("tbody", { children: /* @__PURE__ */ jsx30(ListItems, { dataKey, type, query }) }) }) }) }) })
4958
+ /* @__PURE__ */ jsx30("div", { className: cn("min-h-0 grow", selectedListItem && "hidden"), children: /* @__PURE__ */ jsx30(InfiniteScroll, { query, children: /* @__PURE__ */ jsx30("div", { className: "pr-3", children: /* @__PURE__ */ jsx30("table", { className: "w-full", children: /* @__PURE__ */ jsx30(ItemContextMenu, { dataKey, type, children: /* @__PURE__ */ jsx30("tbody", { children: /* @__PURE__ */ jsx30(ListItems, { dataKey, type, query }) }) }) }) }) }) })
4927
4959
  ] });
4928
4960
  };
4929
4961
  var ListItems = ({
@@ -4934,41 +4966,45 @@ var ListItems = ({
4934
4966
  const { setSelectedListItem } = useDatabrowserStore();
4935
4967
  const keys = useMemo5(() => query.data?.pages.flatMap((page) => page.keys) ?? [], [query.data]);
4936
4968
  const { mutate: editItem } = useEditListItem();
4937
- return /* @__PURE__ */ jsx30(Fragment5, { children: keys.map(({ key, value }, i) => /* @__PURE__ */ jsx30(
4938
- ItemContextMenu,
4969
+ return /* @__PURE__ */ jsx30(Fragment5, { children: keys.map(({ key, value }, i) => /* @__PURE__ */ jsxs20(
4970
+ "tr",
4939
4971
  {
4940
- dataKey,
4941
- type,
4942
- itemKey: key,
4943
- itemValue: value,
4944
- children: /* @__PURE__ */ jsxs20(
4945
- "tr",
4946
- {
4947
- onClick: () => {
4948
- setSelectedListItem({ key, value });
4949
- },
4950
- className: "h-10 border-b border-b-zinc-100 hover:bg-zinc-50",
4951
- children: [
4952
- /* @__PURE__ */ jsx30(
4953
- "td",
4954
- {
4955
- className: cn(
4956
- "cursor-pointer truncate px-3",
4957
- type === "list" || type === "stream" ? "w-32 min-w-24" : "max-w-0"
4958
- ),
4959
- children: key
4960
- }
4961
- ),
4962
- value !== void 0 && /* @__PURE__ */ jsx30(
4963
- "td",
4964
- {
4965
- className: cn("cursor-pointer truncate px-3", type === "zset" ? "w-24" : "max-w-0"),
4966
- children: value
4967
- }
4972
+ "data-item-key": key,
4973
+ "data-item-value": value,
4974
+ onClick: () => {
4975
+ setSelectedListItem({ key, value });
4976
+ },
4977
+ className: "h-10 border-b border-b-zinc-100 hover:bg-zinc-50",
4978
+ children: [
4979
+ /* @__PURE__ */ jsx30(
4980
+ "td",
4981
+ {
4982
+ className: cn(
4983
+ "cursor-pointer truncate px-3",
4984
+ type === "list" || type === "stream" ? "w-32 min-w-24" : "max-w-0"
4968
4985
  ),
4969
- type !== "stream" && /* @__PURE__ */ jsx30("td", { width: 20, className: "px-3", children: /* @__PURE__ */ jsx30(
4986
+ children: key
4987
+ }
4988
+ ),
4989
+ value !== void 0 && /* @__PURE__ */ jsx30(
4990
+ "td",
4991
+ {
4992
+ className: cn("cursor-pointer truncate px-3", type === "zset" ? "w-24" : "max-w-0"),
4993
+ children: value
4994
+ }
4995
+ ),
4996
+ type !== "stream" && /* @__PURE__ */ jsx30(
4997
+ "td",
4998
+ {
4999
+ width: 20,
5000
+ className: "px-3",
5001
+ onClick: (e) => {
5002
+ e.stopPropagation();
5003
+ },
5004
+ children: /* @__PURE__ */ jsx30(
4970
5005
  DeleteAlertDialog,
4971
5006
  {
5007
+ deletionType: "item",
4972
5008
  onDeleteConfirm: (e) => {
4973
5009
  e.stopPropagation();
4974
5010
  editItem({
@@ -4981,10 +5017,10 @@ var ListItems = ({
4981
5017
  },
4982
5018
  children: /* @__PURE__ */ jsx30(Button, { size: "icon-sm", variant: "secondary", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx30(IconTrash, { className: "size-4 text-zinc-500" }) })
4983
5019
  }
4984
- ) })
4985
- ]
4986
- }
4987
- )
5020
+ )
5021
+ }
5022
+ )
5023
+ ]
4988
5024
  },
4989
5025
  `${dataKey}-${key}-${i}`
4990
5026
  )) });
@@ -5060,6 +5096,7 @@ var DataDisplay = () => {
5060
5096
 
5061
5097
  // src/components/databrowser/components/add-key-modal.tsx
5062
5098
  import { useState as useState8 } from "react";
5099
+ import { DialogDescription as DialogDescription2 } from "@radix-ui/react-dialog";
5063
5100
  import { PlusIcon } from "@radix-ui/react-icons";
5064
5101
  import { Controller as Controller3, useForm as useForm4 } from "react-hook-form";
5065
5102
 
@@ -5207,6 +5244,7 @@ function AddKeyModal() {
5207
5244
  /* @__PURE__ */ jsx34(DialogTrigger, { asChild: true, children: /* @__PURE__ */ jsx34(Button, { variant: "primary", size: "icon-sm", children: /* @__PURE__ */ jsx34(PlusIcon, { className: "size-4" }) }) }),
5208
5245
  /* @__PURE__ */ jsxs23(DialogContent, { className: "max-w-[400px]", children: [
5209
5246
  /* @__PURE__ */ jsx34(DialogHeader, { children: /* @__PURE__ */ jsx34(DialogTitle, { children: "Create new key" }) }),
5247
+ /* @__PURE__ */ jsx34("div", { className: "sr-only", children: /* @__PURE__ */ jsx34(DialogDescription2, { children: "Create new key" }) }),
5210
5248
  /* @__PURE__ */ jsxs23("form", { className: "mt-4", onSubmit, children: [
5211
5249
  /* @__PURE__ */ jsxs23("div", { className: "flex gap-1", children: [
5212
5250
  /* @__PURE__ */ jsx34(
@@ -5268,16 +5306,15 @@ var Empty = () => {
5268
5306
  import { useState as useState9 } from "react";
5269
5307
  import { ContextMenuSeparator as ContextMenuSeparator3 } from "@radix-ui/react-context-menu";
5270
5308
  import { Fragment as Fragment8, jsx as jsx36, jsxs as jsxs25 } from "react/jsx-runtime";
5271
- var SidebarContextMenu = ({
5272
- children,
5273
- dataKey
5274
- }) => {
5309
+ var SidebarContextMenu = ({ children }) => {
5275
5310
  const { mutate: deleteKey } = useDeleteKey();
5276
5311
  const [isAlertOpen, setAlertOpen] = useState9(false);
5312
+ const [dataKey, setDataKey] = useState9("");
5277
5313
  return /* @__PURE__ */ jsxs25(Fragment8, { children: [
5278
5314
  /* @__PURE__ */ jsx36(
5279
5315
  DeleteAlertDialog,
5280
5316
  {
5317
+ deletionType: "key",
5281
5318
  open: isAlertOpen,
5282
5319
  onOpenChange: setAlertOpen,
5283
5320
  onDeleteConfirm: (e) => {
@@ -5288,7 +5325,21 @@ var SidebarContextMenu = ({
5288
5325
  }
5289
5326
  ),
5290
5327
  /* @__PURE__ */ jsxs25(ContextMenu, { children: [
5291
- /* @__PURE__ */ jsx36(ContextMenuTrigger, { asChild: true, children }),
5328
+ /* @__PURE__ */ jsx36(
5329
+ ContextMenuTrigger,
5330
+ {
5331
+ onContextMenu: (e) => {
5332
+ const el = e.target;
5333
+ const key = el.closest("[data-key]");
5334
+ if (key && key instanceof HTMLElement && key.dataset.key !== void 0) {
5335
+ setDataKey(key.dataset.key);
5336
+ } else {
5337
+ throw new Error("Key not found");
5338
+ }
5339
+ },
5340
+ children
5341
+ }
5342
+ ),
5292
5343
  /* @__PURE__ */ jsxs25(ContextMenuContent, { children: [
5293
5344
  /* @__PURE__ */ jsx36(
5294
5345
  ContextMenuItem,
@@ -5310,10 +5361,10 @@ var SidebarContextMenu = ({
5310
5361
  };
5311
5362
 
5312
5363
  // src/components/databrowser/components/sidebar/keys-list.tsx
5313
- import { jsx as jsx37, jsxs as jsxs26 } from "react/jsx-runtime";
5364
+ import { Fragment as Fragment9, jsx as jsx37, jsxs as jsxs26 } from "react/jsx-runtime";
5314
5365
  var KeysList = () => {
5315
5366
  const { keys } = useKeys();
5316
- return /* @__PURE__ */ jsx37("div", { className: "pr-3", children: keys.map((data, i) => /* @__PURE__ */ jsx37(KeyItem, { nextKey: keys.at(i + 1)?.[0] ?? "", data }, data[0])) });
5367
+ return /* @__PURE__ */ jsx37("div", { className: "pr-3", children: /* @__PURE__ */ jsx37(SidebarContextMenu, { children: /* @__PURE__ */ jsx37(Fragment9, { children: keys.map((data, i) => /* @__PURE__ */ jsx37(KeyItem, { nextKey: keys.at(i + 1)?.[0] ?? "", data }, data[0])) }) }) });
5317
5368
  };
5318
5369
  var keyStyles = {
5319
5370
  string: "border-sky-400 !bg-sky-50 text-sky-900",
@@ -5329,7 +5380,7 @@ var KeyItem = ({ data, nextKey }) => {
5329
5380
  const [dataKey, dataType] = data;
5330
5381
  const isKeySelected = selectedKey === dataKey;
5331
5382
  const isNextKeySelected = selectedKey === nextKey;
5332
- return /* @__PURE__ */ jsx37(SidebarContextMenu, { dataKey, children: /* @__PURE__ */ jsxs26(
5383
+ return /* @__PURE__ */ jsxs26(
5333
5384
  Button,
5334
5385
  {
5335
5386
  "data-key": dataKey,
@@ -5347,7 +5398,7 @@ var KeyItem = ({ data, nextKey }) => {
5347
5398
  !isKeySelected && !isNextKeySelected && /* @__PURE__ */ jsx37("span", { className: "absolute -bottom-px left-3 right-3 h-px bg-zinc-100" })
5348
5399
  ]
5349
5400
  }
5350
- ) }, dataKey);
5401
+ );
5351
5402
  };
5352
5403
 
5353
5404
  // src/components/databrowser/components/sidebar/search-input.tsx
@@ -5441,8 +5492,8 @@ function Sidebar() {
5441
5492
  import { jsx as jsx42, jsxs as jsxs31 } from "react/jsx-runtime";
5442
5493
  var RedisBrowser = ({ token, url }) => {
5443
5494
  const credentials = useMemo6(() => ({ token, url }), [token, url]);
5444
- return /* @__PURE__ */ jsx42(QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsx42(TooltipProvider, { children: /* @__PURE__ */ jsx42(DatabrowserProvider, { redisCredentials: credentials, children: /* @__PURE__ */ jsxs31(KeysProvider, { children: [
5445
- /* @__PURE__ */ jsx42("div", { className: "ups-db", style: { height: "100%" }, children: /* @__PURE__ */ jsxs31(
5495
+ return /* @__PURE__ */ jsx42(QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsx42(TooltipProvider, { children: /* @__PURE__ */ jsx42(DatabrowserProvider, { redisCredentials: credentials, children: /* @__PURE__ */ jsx42(KeysProvider, { children: /* @__PURE__ */ jsxs31("div", { className: "ups-db", style: { height: "100%" }, children: [
5496
+ /* @__PURE__ */ jsxs31(
5446
5497
  PanelGroup,
5447
5498
  {
5448
5499
  autoSaveId: "persistence",
@@ -5461,9 +5512,9 @@ var RedisBrowser = ({ token, url }) => {
5461
5512
  /* @__PURE__ */ jsx42(Panel, { minSize: 40, children: /* @__PURE__ */ jsx42(DataDisplay, {}) })
5462
5513
  ]
5463
5514
  }
5464
- ) }),
5515
+ ),
5465
5516
  /* @__PURE__ */ jsx42(Toaster, {})
5466
- ] }) }) }) });
5517
+ ] }) }) }) }) });
5467
5518
  };
5468
5519
  export {
5469
5520
  RedisBrowser
package/package.json CHANGED
@@ -1 +1 @@
1
- { "name": "@upstash/react-redis-browser", "version": "v0.1.2-canary-4", "main": "./dist/index.js", "types": "./dist/index.d.ts", "license": "MIT", "private": false, "publishConfig": { "access": "public" }, "bugs": { "url": "https://github.com/upstash/react-redis-browser/issues" }, "homepage": "https://github.com/upstash/react-redis-browser", "files": [ "./dist/**" ], "scripts": { "build": "tsup", "dev": "vite", "lint": "tsc && eslint", "fmt": "prettier --write ." }, "lint-staged": { "**/*.{js,ts,tsx}": [ "prettier --write", "eslint --fix" ] }, "dependencies": { "@ianvs/prettier-plugin-sort-imports": "^4.4.0", "@monaco-editor/react": "^4.6.0", "@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-context-menu": "^2.2.2", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-icons": "1.3.0", "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-scroll-area": "^1.0.3", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-tooltip": "^1.0.7", "@tabler/icons-react": "^3.19.0", "@tanstack/react-query": "^5.32.0", "@types/bytes": "^3.1.4", "@upstash/redis": "^1.31.6", "bytes": "^3.1.2", "react-hook-form": "^7.53.0", "react-resizable-panels": "^2.1.4", "zustand": "5.0.0" }, "devDependencies": { "postcss-prefix-selector": "^2.1.0", "@types/node": "^22.8.4", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@typescript-eslint/eslint-plugin": "8.4.0", "@typescript-eslint/parser": "8.4.0", "@vitejs/plugin-react": "^4.1.0", "autoprefixer": "^10.4.14", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "eslint": "9.10.0", "eslint-plugin-unicorn": "55.0.0", "postcss": "^8.4.31", "prettier": "^3.0.3", "prettier-plugin-tailwindcss": "^0.5.5", "react": "^18.3.1", "react-dom": "^18.3.1", "tailwind-merge": "^2.5.4", "tailwindcss": "^3.4.14", "tailwindcss-animate": "^1.0.7", "tsup": "^8.3.5", "typescript": "^5.0.4", "vite": "^5.4.10", "vite-tsconfig-paths": "^5.0.1" }, "peerDependencies": { "react": "^18.2.0 || ^19", "react-dom": "^18.2.0 || ^19" } }
1
+ { "name": "@upstash/react-redis-browser", "version": "v0.1.2-canary-6", "main": "./dist/index.js", "types": "./dist/index.d.ts", "license": "MIT", "private": false, "publishConfig": { "access": "public" }, "bugs": { "url": "https://github.com/upstash/react-redis-browser/issues" }, "homepage": "https://github.com/upstash/react-redis-browser", "files": [ "./dist/**" ], "scripts": { "build": "tsup", "dev": "vite", "lint": "tsc && eslint", "fmt": "prettier --write ." }, "lint-staged": { "**/*.{js,ts,tsx}": [ "prettier --write", "eslint --fix" ] }, "dependencies": { "@ianvs/prettier-plugin-sort-imports": "^4.4.0", "@monaco-editor/react": "^4.6.0", "@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-context-menu": "^2.2.2", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-icons": "1.3.0", "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-scroll-area": "^1.0.3", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-tooltip": "^1.0.7", "@tabler/icons-react": "^3.19.0", "@tanstack/react-query": "^5.32.0", "@types/bytes": "^3.1.4", "@upstash/redis": "^1.31.6", "bytes": "^3.1.2", "react-hook-form": "^7.53.0", "react-resizable-panels": "^2.1.4", "zustand": "5.0.0" }, "devDependencies": { "postcss-prefix-selector": "^2.1.0", "@types/node": "^22.8.4", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@typescript-eslint/eslint-plugin": "8.4.0", "@typescript-eslint/parser": "8.4.0", "@vitejs/plugin-react": "^4.1.0", "autoprefixer": "^10.4.14", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "eslint": "9.10.0", "eslint-plugin-unicorn": "55.0.0", "postcss": "^8.4.31", "prettier": "^3.0.3", "prettier-plugin-tailwindcss": "^0.5.5", "react": "^18.3.1", "react-dom": "^18.3.1", "tailwind-merge": "^2.5.4", "tailwindcss": "^3.4.14", "tailwindcss-animate": "^1.0.7", "tsup": "^8.3.5", "typescript": "^5.0.4", "vite": "^5.4.10", "vite-tsconfig-paths": "^5.0.1" }, "peerDependencies": { "react": "^18.2.0 || ^19", "react-dom": "^18.2.0 || ^19" } }