@upstash/react-redis-browser 0.2.14-rc.11 → 0.2.14-rc.12
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/README.md +73 -6
- package/dist/index.css +15 -5
- package/dist/index.js +736 -284
- package/dist/index.mjs +863 -411
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# Redis Browser for Upstash Redis - [Preview](https://react-redis-browser.vercel.app/)
|
|
2
|
+
|
|
2
3
|
`@upstash/react-redis-browser` is a React component that provides a UI for browsing and editing data in your Upstash Redis instances.
|
|
3
4
|
|
|
4
5
|
<img src="https://github.com/user-attachments/assets/1b714616-310b-4250-9f92-cc28ed9881cd" width="640px" />
|
|
@@ -17,15 +18,11 @@ Here's a basic example of how to use the component:
|
|
|
17
18
|
|
|
18
19
|
```tsx
|
|
19
20
|
import { RedisBrowser } from "@upstash/react-redis-browser"
|
|
21
|
+
|
|
20
22
|
import "@upstash/react-redis-browser/dist/index.css"
|
|
21
23
|
|
|
22
24
|
export default function Page() {
|
|
23
|
-
return
|
|
24
|
-
<RedisBrowser
|
|
25
|
-
url={UPSTASH_REDIS_REST_URL}
|
|
26
|
-
token={UPSTASH_REDIS_REST_TOKEN}
|
|
27
|
-
/>
|
|
28
|
-
)
|
|
25
|
+
return <RedisBrowser url={UPSTASH_REDIS_REST_URL} token={UPSTASH_REDIS_REST_TOKEN} />
|
|
29
26
|
}
|
|
30
27
|
```
|
|
31
28
|
|
|
@@ -35,6 +32,7 @@ The state of the databrowser can be persisted using the `storage` property.
|
|
|
35
32
|
|
|
36
33
|
```tsx
|
|
37
34
|
import { RedisBrowser } from "@upstash/react-redis-browser"
|
|
35
|
+
|
|
38
36
|
import "@upstash/react-redis-browser/dist/index.css"
|
|
39
37
|
|
|
40
38
|
export default function Page() {
|
|
@@ -50,3 +48,72 @@ export default function Page() {
|
|
|
50
48
|
)
|
|
51
49
|
}
|
|
52
50
|
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Codebase Internals
|
|
55
|
+
|
|
56
|
+
### Query Data Flow (UI ↔ Code ↔ Store ↔ Redis)
|
|
57
|
+
|
|
58
|
+
The query is stored in the zustand store as a **JS object literal string** (not JSON — keys are unquoted like `$and:` instead of `"$and":`). This single string is the shared source of truth between both the UI query builder and the code editor.
|
|
59
|
+
|
|
60
|
+
**Store shape:** `ValuesSearchFilter.queries` is a `Record<string, string>` mapping `indexName → queryString`. Each index remembers its own query independently.
|
|
61
|
+
|
|
62
|
+
**Code editor flow (`query-builder.tsx`):** Prepends `"const query: Query = "` to the stored string for Monaco. On edit, strips that prefix and writes the raw string to the store. Monaco gets type definitions generated from the index schema, giving autocomplete.
|
|
63
|
+
|
|
64
|
+
**UI query builder flow:** The UI works with a tree (`QueryState` — groups and conditions with IDs). `useQueryStateSync` handles bidirectional sync:
|
|
65
|
+
|
|
66
|
+
- **Store → tree:** `parseQueryString` (`query-parser.ts`) parses the string into `QueryNode`s. Runs on mount and when the store query changes externally (e.g. user edited in code mode then switched to UI).
|
|
67
|
+
- **Tree → store:** When the UI mutates the tree, `stringifyQueryState` (`query-stringify.ts`) converts back to a string and writes to the store.
|
|
68
|
+
- **Sync guard:** `isOurUpdate` ref prevents the hook from re-parsing its own writes. Only external changes trigger re-parsing.
|
|
69
|
+
|
|
70
|
+
**Limitation:** `$must`/`$should` combination is not supported in the UI builder — `hasMustShouldCombination` check blocks switching to UI mode when this pattern exists. The UI normalizes `$must`→`$and` and `$should`→`$or`.
|
|
71
|
+
|
|
72
|
+
**Store → Redis:** `KeysProvider` (`use-keys.tsx`) reads `valuesSearch.query` from the store, parses it with `parseJSObjectLiteral` into a plain object, and passes it as the `filter` to `redis.search.index().query()`.
|
|
73
|
+
|
|
74
|
+
### Schema Data Flow
|
|
75
|
+
|
|
76
|
+
Schemas define the structure of a search index. Two formats exist:
|
|
77
|
+
|
|
78
|
+
- **API format** — flat, dot-notation keys: `{ "name": "TEXT", "contact.email": "TEXT" }`
|
|
79
|
+
- **Editor format** — nested TypeScript builder DSL: `s.object({ name: s.string(), contact: s.object({ email: s.string() }) })`
|
|
80
|
+
|
|
81
|
+
`parseSchemaFromEditorValue` (`schema-parser.ts`) converts editor → API by recursively walking the builder calls, flattening nested `s.object()` calls into dot-separated keys, and mapping `s.string()` → `"TEXT"`, `s.number()` → `"F64"`, etc.
|
|
82
|
+
|
|
83
|
+
`schemaToEditorValue` converts API → editor by unflattening dot-notation keys into a nested object, then rendering back to the builder DSL.
|
|
84
|
+
|
|
85
|
+
`generateSchemaTypeDefinitions` produces TypeScript class declarations (the `s` builder, field builders, `Schema` type) that are injected into Monaco for autocomplete in the schema editor. The type definitions in `search-types-file.ts` mirror the `@upstash/redis` SDK types.
|
|
86
|
+
|
|
87
|
+
### UI Query Builder Mutations
|
|
88
|
+
|
|
89
|
+
All mutations go through `QueryBuilderUIProvider` (`query-builder-context.tsx`), which exposes four operations via context: `updateNode`, `deleteNode`, `addChildToGroup`, `moveNode`.
|
|
90
|
+
|
|
91
|
+
Every node in the tree has a unique `id`. All operations traverse the tree by ID, apply the change immutably, and return a new tree. The flow: component calls e.g. `updateNode(id, updates)` → `setQueryState` from `useQueryStateSync` receives a modifier → modifier gets a `structuredClone` of current state → tree is modified → result is stringified and written to the zustand store.
|
|
92
|
+
|
|
93
|
+
### Drag-and-Drop in the UI Query Builder
|
|
94
|
+
|
|
95
|
+
Uses `@dnd-kit/core`. Files: `dnd-context.tsx`, `draggable-item.tsx`, `drop-zone.tsx`, `drag-overlay.tsx`.
|
|
96
|
+
|
|
97
|
+
**Components and responsibilities:**
|
|
98
|
+
|
|
99
|
+
- **`DraggableItem`** — wraps each condition row and group row, provides the drag handle
|
|
100
|
+
- **`DropIndicator`** (`drop-zone.tsx`) — a thin horizontal line rendered _between_ each child in a group. Each one is a droppable zone.
|
|
101
|
+
- **`EmptyGroupDropZone`** (`drop-zone.tsx`) — rendered inside empty groups as a dashed "Add a condition" button that also accepts drops
|
|
102
|
+
- **`QueryDragOverlay`** — portal overlay showing a preview of the dragged node while dragging
|
|
103
|
+
- **`QueryDndProvider`** (`dnd-context.tsx`) — wraps the tree, owns the `@dnd-kit` `DndContext`, handles all drag events
|
|
104
|
+
|
|
105
|
+
**How drop zones work:** `QueryGroup` renders a `DropIndicator` before each child and one at the end. Each drop zone has an ID following the pattern `drop-{groupId}-{childId}` (insert before that child) or `drop-{groupId}-end` (append). This encoding lets `onDragEnd` know both _which group_ and _which position_ to insert at by just parsing the drop zone ID.
|
|
106
|
+
|
|
107
|
+
On drop, `dnd-context.tsx` resolves the source/target from the IDs, handles edge cases (no-op for same position, prevents dropping a group into its own descendants), and calls `moveNode` from the query builder context.
|
|
108
|
+
|
|
109
|
+
### Keys List (`useKeys`)
|
|
110
|
+
|
|
111
|
+
`KeysProvider` wraps `DatabrowserInstance` and provides fetched keys via `useKeys()`. It's at root level because multiple children need it: the **sidebar** renders the key list, and the **data display** uses `useKeyType(selectedKey)` (which looks up the type from the fetched keys array) to decide which editor to render for the selected key, and uses `query.isLoading` to show a loading state.
|
|
112
|
+
|
|
113
|
+
**Dual-mode fetching** based on `isValuesSearchSelected`:
|
|
114
|
+
|
|
115
|
+
1. **Standard Redis SCAN:** Sends `SCAN` commands with optional `MATCH` pattern and `TYPE` filter. When SCAN returns empty pages (sparse databases + type filters), retries with increasing `COUNT` (`100 → 300 → 500`) to reduce round trips.
|
|
116
|
+
|
|
117
|
+
2. **Redis Search:** Calls `redis.search.index().query()` with the parsed filter object from the store, offset-based pagination, and `select: {}` (returns only keys + scores).
|
|
118
|
+
|
|
119
|
+
Uses React Query's `useInfiniteQuery` for cursor-based pagination. Key types from the scan are cached individually so `useFetchKeyType` (used elsewhere) doesn't need extra `TYPE` commands. Results are deduplicated since Redis SCAN can return duplicates across pages.
|
package/dist/index.css
CHANGED
|
@@ -973,6 +973,9 @@
|
|
|
973
973
|
.ups-db .inline-block {
|
|
974
974
|
display: inline-block;
|
|
975
975
|
}
|
|
976
|
+
.ups-db .\!inline {
|
|
977
|
+
display: inline !important;
|
|
978
|
+
}
|
|
976
979
|
.ups-db .inline {
|
|
977
980
|
display: inline;
|
|
978
981
|
}
|
|
@@ -1186,6 +1189,10 @@
|
|
|
1186
1189
|
.ups-db .w-full {
|
|
1187
1190
|
width: 100%;
|
|
1188
1191
|
}
|
|
1192
|
+
.ups-db .w-max {
|
|
1193
|
+
width: -moz-max-content;
|
|
1194
|
+
width: max-content;
|
|
1195
|
+
}
|
|
1189
1196
|
.ups-db .min-w-0 {
|
|
1190
1197
|
min-width: 0px;
|
|
1191
1198
|
}
|
|
@@ -1433,8 +1440,8 @@
|
|
|
1433
1440
|
.ups-db .overflow-hidden {
|
|
1434
1441
|
overflow: hidden;
|
|
1435
1442
|
}
|
|
1436
|
-
.ups-db
|
|
1437
|
-
overflow: visible;
|
|
1443
|
+
.ups-db .\!overflow-visible {
|
|
1444
|
+
overflow: visible !important;
|
|
1438
1445
|
}
|
|
1439
1446
|
.ups-db .overflow-x-auto {
|
|
1440
1447
|
overflow-x: auto;
|
|
@@ -1445,9 +1452,6 @@
|
|
|
1445
1452
|
.ups-db .overflow-x-hidden {
|
|
1446
1453
|
overflow-x: hidden;
|
|
1447
1454
|
}
|
|
1448
|
-
.ups-db .overflow-x-scroll {
|
|
1449
|
-
overflow-x: scroll;
|
|
1450
|
-
}
|
|
1451
1455
|
.ups-db .truncate {
|
|
1452
1456
|
overflow: hidden;
|
|
1453
1457
|
text-overflow: ellipsis;
|
|
@@ -1933,6 +1937,9 @@
|
|
|
1933
1937
|
padding-top: 8px;
|
|
1934
1938
|
padding-bottom: 8px;
|
|
1935
1939
|
}
|
|
1940
|
+
.ups-db .pb-2 {
|
|
1941
|
+
padding-bottom: 0.5rem;
|
|
1942
|
+
}
|
|
1936
1943
|
.ups-db .pb-5 {
|
|
1937
1944
|
padding-bottom: 1.25rem;
|
|
1938
1945
|
}
|
|
@@ -1975,6 +1982,9 @@
|
|
|
1975
1982
|
.ups-db .pt-0\.5 {
|
|
1976
1983
|
padding-top: 0.125rem;
|
|
1977
1984
|
}
|
|
1985
|
+
.ups-db .pt-1 {
|
|
1986
|
+
padding-top: 0.25rem;
|
|
1987
|
+
}
|
|
1978
1988
|
.ups-db .text-left {
|
|
1979
1989
|
text-align: left;
|
|
1980
1990
|
}
|