nestjs-redis-ui 0.1.1

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.
Files changed (38) hide show
  1. package/README.md +256 -0
  2. package/dist/redis/constants/redis.constants.d.ts +2 -0
  3. package/dist/redis/constants/redis.constants.js +6 -0
  4. package/dist/redis/constants/redis.constants.js.map +1 -0
  5. package/dist/redis/index.d.ts +6 -0
  6. package/dist/redis/index.js +15 -0
  7. package/dist/redis/index.js.map +1 -0
  8. package/dist/redis/interfaces/index.d.ts +3 -0
  9. package/dist/redis/interfaces/index.js +3 -0
  10. package/dist/redis/interfaces/index.js.map +1 -0
  11. package/dist/redis/interfaces/redis-module-options.interface.d.ts +20 -0
  12. package/dist/redis/interfaces/redis-module-options.interface.js +3 -0
  13. package/dist/redis/interfaces/redis-module-options.interface.js.map +1 -0
  14. package/dist/redis/interfaces/redis-ui-options.interface.d.ts +18 -0
  15. package/dist/redis/interfaces/redis-ui-options.interface.js +3 -0
  16. package/dist/redis/interfaces/redis-ui-options.interface.js.map +1 -0
  17. package/dist/redis/interfaces/redis-ui-store.interface.d.ts +11 -0
  18. package/dist/redis/interfaces/redis-ui-store.interface.js +3 -0
  19. package/dist/redis/interfaces/redis-ui-store.interface.js.map +1 -0
  20. package/dist/redis/providers/redis.providers.d.ts +4 -0
  21. package/dist/redis/providers/redis.providers.js +60 -0
  22. package/dist/redis/providers/redis.providers.js.map +1 -0
  23. package/dist/redis/redis.module.d.ts +6 -0
  24. package/dist/redis/redis.module.js +49 -0
  25. package/dist/redis/redis.module.js.map +1 -0
  26. package/dist/redis/services/redis.service.d.ts +15 -0
  27. package/dist/redis/services/redis.service.js +83 -0
  28. package/dist/redis/services/redis.service.js.map +1 -0
  29. package/dist/redis/ui/assets/client.js +203 -0
  30. package/dist/redis/ui/assets/index.html +46 -0
  31. package/dist/redis/ui/assets/styles.css +266 -0
  32. package/dist/redis/ui/redis-ui.d.ts +13 -0
  33. package/dist/redis/ui/redis-ui.js +117 -0
  34. package/dist/redis/ui/redis-ui.js.map +1 -0
  35. package/dist/redis/ui/resolve-redis-ui-options.d.ts +10 -0
  36. package/dist/redis/ui/resolve-redis-ui-options.js +25 -0
  37. package/dist/redis/ui/resolve-redis-ui-options.js.map +1 -0
  38. package/package.json +113 -0
@@ -0,0 +1,203 @@
1
+ const REDIS_UI_PATH = document.documentElement.dataset.redisUiPath;
2
+ const ALLOW_DELETE = document.documentElement.dataset.redisUiAllowDelete === "true";
3
+ const DEFAULT_PATTERN = document.documentElement.dataset.redisUiDefaultPattern || "*";
4
+ const AUTO_REFRESH_MS = 1000;
5
+
6
+ const patternInput = document.getElementById("pattern");
7
+ const keysEl = document.getElementById("keys");
8
+ const detailEl = document.getElementById("detail");
9
+ const keyCountEl = document.getElementById("keyCount");
10
+ const selectedKeyLabel = document.getElementById("selectedKeyLabel");
11
+ const autoRefreshBtn = document.getElementById("autoRefreshBtn");
12
+ let selectedKey = null;
13
+ let autoRefreshTimer = null;
14
+ let refreshInFlight = false;
15
+
16
+ async function fetchKeys(options = {}) {
17
+ const silent = Boolean(options.silent);
18
+ const preserveSelection = Boolean(options.preserveSelection);
19
+ const pattern = patternInput.value.trim() || DEFAULT_PATTERN;
20
+ const previousSelected = preserveSelection ? selectedKey : null;
21
+
22
+ if (!silent) {
23
+ keysEl.innerHTML = '<div class="empty">불러오는 중...</div>';
24
+ detailEl.innerHTML = '<div class="empty">키를 선택하면 값이 표시됩니다.</div>';
25
+ selectedKey = null;
26
+ selectedKeyLabel.textContent = "-";
27
+ }
28
+
29
+ try {
30
+ const res = await fetch("/" + REDIS_UI_PATH + "/api/keys?pattern=" + encodeURIComponent(pattern));
31
+ const data = await res.json();
32
+ if (!res.ok) throw new Error(data.message || "키 조회 실패");
33
+
34
+ keyCountEl.textContent = String(data.count);
35
+ if (!data.keys.length) {
36
+ keysEl.innerHTML = '<div class="empty">매칭되는 키가 없습니다.</div>';
37
+ if (silent && previousSelected) {
38
+ selectedKey = null;
39
+ selectedKeyLabel.textContent = "-";
40
+ detailEl.innerHTML = '<div class="empty">키가 삭제되었거나 만료되었습니다.</div>';
41
+ }
42
+ return;
43
+ }
44
+
45
+ keysEl.innerHTML = "";
46
+ let activeItem = null;
47
+ data.keys.forEach(key => {
48
+ const item = document.createElement("div");
49
+ item.className = "key-item";
50
+ item.textContent = key;
51
+ item.title = key;
52
+ item.addEventListener("click", () => selectKey(key, item));
53
+ if (previousSelected && key === previousSelected) {
54
+ item.classList.add("active");
55
+ activeItem = item;
56
+ selectedKey = key;
57
+ }
58
+ keysEl.appendChild(item);
59
+ });
60
+
61
+ if (silent && previousSelected && !activeItem) {
62
+ selectedKey = null;
63
+ selectedKeyLabel.textContent = "-";
64
+ detailEl.innerHTML = '<div class="empty">키가 삭제되었거나 만료되었습니다.</div>';
65
+ }
66
+ } catch (error) {
67
+ if (!silent) {
68
+ keysEl.innerHTML = '<div class="error">' + (error.message || error) + "</div>";
69
+ keyCountEl.textContent = "0";
70
+ }
71
+ }
72
+ }
73
+
74
+ async function selectKey(key, element, options = {}) {
75
+ const silent = Boolean(options.silent);
76
+ selectedKey = key;
77
+ selectedKeyLabel.textContent = key;
78
+ document.querySelectorAll(".key-item").forEach(el => el.classList.remove("active"));
79
+ if (element) element.classList.add("active");
80
+ if (!silent) {
81
+ detailEl.innerHTML = '<div class="empty">불러오는 중...</div>';
82
+ }
83
+
84
+ try {
85
+ const res = await fetch("/" + REDIS_UI_PATH + "/api/entry?key=" + encodeURIComponent(key));
86
+ const data = await res.json();
87
+ if (!res.ok) throw new Error(data.message || "값 조회 실패");
88
+
89
+ // 자동 새로고침 중 다른 키로 바뀐 경우 무시
90
+ if (selectedKey !== key) return;
91
+
92
+ const pretty = typeof data.value === "string" ? data.value : JSON.stringify(data.value, null, 2);
93
+ const deleteButton = ALLOW_DELETE ? '<button id="deleteEntryBtn" class="danger" type="button">키 삭제</button>' : "";
94
+
95
+ detailEl.innerHTML =
96
+ '<div class="actions">' +
97
+ '<button id="reloadEntryBtn" class="secondary" type="button">값 새로고침</button>' +
98
+ deleteButton +
99
+ "</div>" +
100
+ '<div class="meta">' +
101
+ '<div class="label">Key</div><div class="value">' +
102
+ escapeHtml(data.key) +
103
+ "</div>" +
104
+ '<div class="label">Type</div><div class="value">' +
105
+ escapeHtml(String(data.type)) +
106
+ "</div>" +
107
+ '<div class="label">TTL</div><div class="value">' +
108
+ formatTtl(data.ttl) +
109
+ "</div>" +
110
+ "</div>" +
111
+ "<pre>" +
112
+ escapeHtml(pretty === undefined ? "undefined" : String(pretty)) +
113
+ "</pre>";
114
+
115
+ document.getElementById("reloadEntryBtn").addEventListener("click", () => {
116
+ const active = document.querySelector(".key-item.active");
117
+ selectKey(key, active);
118
+ });
119
+
120
+ if (ALLOW_DELETE) {
121
+ document.getElementById("deleteEntryBtn").addEventListener("click", () => deleteKey(key));
122
+ }
123
+ } catch (error) {
124
+ if (selectedKey !== key) return;
125
+ detailEl.innerHTML = '<div class="error">' + (error.message || error) + "</div>";
126
+ }
127
+ }
128
+
129
+ async function deleteKey(key) {
130
+ if (!ALLOW_DELETE) return;
131
+ if (!confirm("키를 삭제할까요?\n" + key)) return;
132
+ try {
133
+ const res = await fetch("/" + REDIS_UI_PATH + "/api/entry?key=" + encodeURIComponent(key), { method: "DELETE" });
134
+ const data = await res.json();
135
+ if (!res.ok) throw new Error(data.message || "삭제 실패");
136
+ await fetchKeys();
137
+ } catch (error) {
138
+ alert(error.message || error);
139
+ }
140
+ }
141
+
142
+ async function runAutoRefresh() {
143
+ if (refreshInFlight || document.hidden) return;
144
+ refreshInFlight = true;
145
+ try {
146
+ await fetchKeys({ silent: true, preserveSelection: true });
147
+ if (selectedKey) {
148
+ const active = document.querySelector(".key-item.active");
149
+ await selectKey(selectedKey, active, { silent: true });
150
+ }
151
+ } finally {
152
+ refreshInFlight = false;
153
+ }
154
+ }
155
+
156
+ function setAutoRefresh(enabled) {
157
+ if (autoRefreshTimer) {
158
+ clearInterval(autoRefreshTimer);
159
+ autoRefreshTimer = null;
160
+ }
161
+
162
+ autoRefreshBtn.classList.toggle("active", enabled);
163
+ autoRefreshBtn.setAttribute("aria-pressed", String(enabled));
164
+ const label = document.getElementById("autoRefreshLabel");
165
+ if (label) {
166
+ label.textContent = enabled ? "자동 새로고침 ON" : "자동 새로고침";
167
+ }
168
+
169
+ if (enabled) {
170
+ autoRefreshTimer = setInterval(runAutoRefresh, AUTO_REFRESH_MS);
171
+ runAutoRefresh();
172
+ }
173
+ }
174
+
175
+ function formatTtl(ttl) {
176
+ if (ttl === -1) return "없음 (영구)";
177
+ if (ttl === -2) return "키 없음";
178
+ return ttl + "초";
179
+ }
180
+
181
+ function escapeHtml(value) {
182
+ return String(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
183
+ }
184
+
185
+ document.getElementById("refreshBtn").addEventListener("click", () => fetchKeys());
186
+ document.getElementById("clearFilterBtn").addEventListener("click", () => {
187
+ patternInput.value = DEFAULT_PATTERN;
188
+ fetchKeys();
189
+ });
190
+ autoRefreshBtn.addEventListener("click", () => {
191
+ const next = autoRefreshBtn.getAttribute("aria-pressed") !== "true";
192
+ setAutoRefresh(next);
193
+ });
194
+ patternInput.addEventListener("keydown", event => {
195
+ if (event.key === "Enter") fetchKeys();
196
+ });
197
+ document.addEventListener("visibilitychange", () => {
198
+ if (!document.hidden && autoRefreshBtn.getAttribute("aria-pressed") === "true") {
199
+ runAutoRefresh();
200
+ }
201
+ });
202
+
203
+ fetchKeys();
@@ -0,0 +1,46 @@
1
+ <!doctype html>
2
+ <html lang="ko" data-redis-ui-path="__REDIS_UI_PATH__" data-redis-ui-allow-delete="__REDIS_UI_ALLOW_DELETE__" data-redis-ui-default-pattern="__REDIS_UI_DEFAULT_PATTERN__">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>__REDIS_UI_TITLE__</title>
7
+ <link rel="stylesheet" href="/__REDIS_UI_PATH__/static/styles.css" />
8
+ </head>
9
+ <body>
10
+ <header>
11
+ <h1>__REDIS_UI_TITLE__</h1>
12
+ <span class="badge">__REDIS_UI_BADGE__</span>
13
+ </header>
14
+ <div class="toolbar">
15
+ <input id="pattern" type="text" value="__REDIS_UI_DEFAULT_PATTERN__" placeholder="키 패턴 (예: app:*, *cache*)" />
16
+ <button id="refreshBtn" type="button">조회</button>
17
+ <button id="autoRefreshBtn" class="secondary btn-with-icon" type="button" aria-pressed="false">
18
+ <svg class="btn-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
19
+ <path
20
+ fill="currentColor"
21
+ d="M17.65 6.35A7.95 7.95 0 0 0 12 4a8 8 0 1 0 7.75 10h-2.1A6 6 0 1 1 12 6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35Z"
22
+ />
23
+ </svg>
24
+ <span id="autoRefreshLabel">자동 새로고침</span>
25
+ </button>
26
+ <button id="clearFilterBtn" class="secondary" type="button">패턴 초기화</button>
27
+ </div>
28
+ <div class="layout">
29
+ <section class="panel">
30
+ <div class="panel-header">
31
+ <span>Keys</span>
32
+ <span id="keyCount">0</span>
33
+ </div>
34
+ <div id="keys" class="keys"><div class="empty">패턴을 입력하고 조회하세요.</div></div>
35
+ </section>
36
+ <section class="panel panel-right">
37
+ <div class="panel-header">
38
+ <span>Value</span>
39
+ <span id="selectedKeyLabel">-</span>
40
+ </div>
41
+ <div id="detail" class="detail"><div class="empty">키를 선택하면 값이 표시됩니다.</div></div>
42
+ </section>
43
+ </div>
44
+ <script src="/__REDIS_UI_PATH__/static/client.js"></script>
45
+ </body>
46
+ </html>
@@ -0,0 +1,266 @@
1
+ :root {
2
+ --bg: #fafafa;
3
+ --panel: #ffffff;
4
+ --border: #e8e8e8;
5
+ --text: #3b4151;
6
+ --muted: #7d8492;
7
+ --accent: #89bf04;
8
+ --accent-dark: #6b9503;
9
+ --header: #1b1b1b;
10
+ --danger: #d73a49;
11
+ --code-bg: #41444e;
12
+ --code-text: #e8e8e8;
13
+ --row-hover: #f0f6e8;
14
+ --selected: #eaf5d3;
15
+ }
16
+
17
+ * {
18
+ box-sizing: border-box;
19
+ }
20
+
21
+ body {
22
+ margin: 0;
23
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
24
+ color: var(--text);
25
+ background: var(--bg);
26
+ min-height: 100vh;
27
+ }
28
+
29
+ header {
30
+ background: var(--header);
31
+ color: #fff;
32
+ padding: 14px 24px;
33
+ display: flex;
34
+ align-items: center;
35
+ gap: 12px;
36
+ border-bottom: 4px solid var(--accent);
37
+ }
38
+
39
+ header h1 {
40
+ margin: 0;
41
+ font-size: 20px;
42
+ font-weight: 700;
43
+ letter-spacing: 0.2px;
44
+ }
45
+
46
+ header .badge {
47
+ background: var(--accent);
48
+ color: #1b1b1b;
49
+ font-size: 11px;
50
+ font-weight: 700;
51
+ padding: 3px 8px;
52
+ border-radius: 4px;
53
+ }
54
+
55
+ .toolbar {
56
+ display: flex;
57
+ gap: 8px;
58
+ padding: 16px 24px;
59
+ background: var(--panel);
60
+ border-bottom: 1px solid var(--border);
61
+ flex-wrap: wrap;
62
+ align-items: center;
63
+ }
64
+
65
+ .toolbar input {
66
+ flex: 1;
67
+ min-width: 220px;
68
+ height: 36px;
69
+ border: 1px solid var(--border);
70
+ border-radius: 4px;
71
+ padding: 0 12px;
72
+ font-size: 14px;
73
+ outline: none;
74
+ }
75
+
76
+ .toolbar input:focus {
77
+ border-color: var(--accent);
78
+ }
79
+
80
+ button {
81
+ height: 36px;
82
+ border: none;
83
+ border-radius: 4px;
84
+ padding: 0 14px;
85
+ font-size: 13px;
86
+ font-weight: 600;
87
+ cursor: pointer;
88
+ background: var(--accent);
89
+ color: #1b1b1b;
90
+ }
91
+
92
+ button:hover {
93
+ background: var(--accent-dark);
94
+ color: #fff;
95
+ }
96
+
97
+ button.secondary {
98
+ background: #fff;
99
+ border: 1px solid var(--border);
100
+ color: var(--text);
101
+ }
102
+
103
+ button.secondary:hover {
104
+ background: #f3f3f3;
105
+ color: var(--text);
106
+ }
107
+
108
+ button.secondary.active {
109
+ background: var(--selected);
110
+ border-color: var(--accent);
111
+ color: var(--accent-dark);
112
+ }
113
+
114
+ button.secondary.active:hover {
115
+ background: var(--row-hover);
116
+ color: var(--accent-dark);
117
+ }
118
+
119
+ .btn-with-icon {
120
+ display: inline-flex;
121
+ align-items: center;
122
+ gap: 6px;
123
+ }
124
+
125
+ .btn-icon {
126
+ width: 14px;
127
+ height: 14px;
128
+ flex-shrink: 0;
129
+ }
130
+
131
+ button.secondary.active .btn-icon {
132
+ animation: btn-icon-spin 1s linear infinite;
133
+ }
134
+
135
+ @keyframes btn-icon-spin {
136
+ to {
137
+ transform: rotate(360deg);
138
+ }
139
+ }
140
+
141
+ button.danger {
142
+ background: var(--danger);
143
+ color: #fff;
144
+ }
145
+
146
+ button.danger:hover {
147
+ filter: brightness(0.92);
148
+ }
149
+
150
+ button:disabled {
151
+ opacity: 0.5;
152
+ cursor: not-allowed;
153
+ }
154
+
155
+ .layout {
156
+ display: grid;
157
+ grid-template-columns: 360px 1fr;
158
+ gap: 0;
159
+ min-height: calc(100vh - 120px);
160
+ }
161
+
162
+ @media (max-width: 900px) {
163
+ .layout {
164
+ grid-template-columns: 1fr;
165
+ }
166
+ }
167
+
168
+ .panel {
169
+ background: var(--panel);
170
+ border-right: 1px solid var(--border);
171
+ display: flex;
172
+ flex-direction: column;
173
+ min-height: 420px;
174
+ }
175
+
176
+ .panel-right {
177
+ border-right: none;
178
+ }
179
+
180
+ .panel-header {
181
+ padding: 12px 16px;
182
+ border-bottom: 1px solid var(--border);
183
+ display: flex;
184
+ justify-content: space-between;
185
+ align-items: center;
186
+ font-size: 13px;
187
+ color: var(--muted);
188
+ font-weight: 600;
189
+ }
190
+
191
+ .keys {
192
+ overflow: auto;
193
+ flex: 1;
194
+ }
195
+
196
+ .key-item {
197
+ padding: 10px 16px;
198
+ border-bottom: 1px solid var(--border);
199
+ cursor: pointer;
200
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
201
+ font-size: 12px;
202
+ word-break: break-all;
203
+ }
204
+
205
+ .key-item:hover {
206
+ background: var(--row-hover);
207
+ }
208
+
209
+ .key-item.active {
210
+ background: var(--selected);
211
+ border-left: 3px solid var(--accent);
212
+ }
213
+
214
+ .detail {
215
+ padding: 16px 20px;
216
+ overflow: auto;
217
+ flex: 1;
218
+ }
219
+
220
+ .meta {
221
+ display: grid;
222
+ grid-template-columns: 100px 1fr;
223
+ gap: 8px 12px;
224
+ margin-bottom: 16px;
225
+ font-size: 13px;
226
+ }
227
+
228
+ .meta .label {
229
+ color: var(--muted);
230
+ font-weight: 600;
231
+ }
232
+
233
+ .meta .value {
234
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
235
+ word-break: break-all;
236
+ }
237
+
238
+ pre {
239
+ margin: 0;
240
+ background: var(--code-bg);
241
+ color: var(--code-text);
242
+ padding: 16px;
243
+ border-radius: 6px;
244
+ overflow: auto;
245
+ font-size: 12px;
246
+ line-height: 1.5;
247
+ white-space: pre-wrap;
248
+ word-break: break-word;
249
+ }
250
+
251
+ .empty,
252
+ .error {
253
+ padding: 24px 16px;
254
+ color: var(--muted);
255
+ font-size: 14px;
256
+ }
257
+
258
+ .error {
259
+ color: var(--danger);
260
+ }
261
+
262
+ .actions {
263
+ display: flex;
264
+ gap: 8px;
265
+ margin-bottom: 16px;
266
+ }
@@ -0,0 +1,13 @@
1
+ import type { INestApplication } from '@nestjs/common';
2
+ import type { RedisUiOptions } from '../interfaces';
3
+ export declare class RedisUi {
4
+ private readonly app;
5
+ private readonly options;
6
+ private readonly assetDir;
7
+ constructor(app: INestApplication, options: RedisUiOptions);
8
+ static setup(app: INestApplication, options: RedisUiOptions): RedisUi;
9
+ enable(): void;
10
+ private setupRoutes;
11
+ private getHtml;
12
+ private readAsset;
13
+ }
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.RedisUi = void 0;
7
+ const express_basic_auth_1 = __importDefault(require("express-basic-auth"));
8
+ const fs_1 = require("fs");
9
+ const path_1 = require("path");
10
+ const resolve_redis_ui_options_1 = require("./resolve-redis-ui-options");
11
+ class RedisUi {
12
+ app;
13
+ options;
14
+ assetDir = (0, path_1.join)(__dirname, 'assets');
15
+ constructor(app, options) {
16
+ this.app = app;
17
+ this.options = (0, resolve_redis_ui_options_1.resolveRedisUiOptions)(options);
18
+ if (this.options.enabled) {
19
+ this.enable();
20
+ }
21
+ }
22
+ static setup(app, options) {
23
+ return new RedisUi(app, options);
24
+ }
25
+ enable() {
26
+ if (this.options.auth) {
27
+ this.app.use([`/${this.options.path}`], (0, express_basic_auth_1.default)({
28
+ users: {
29
+ [this.options.auth.username]: this.options.auth.password,
30
+ },
31
+ challenge: true,
32
+ }));
33
+ }
34
+ this.setupRoutes();
35
+ }
36
+ setupRoutes() {
37
+ const expressApp = this.app.getHttpAdapter().getInstance();
38
+ const { path, store, allowDelete, defaultPattern } = this.options;
39
+ expressApp.get(`/${path}`, (_req, res) => {
40
+ res.type('html').send(this.getHtml());
41
+ });
42
+ expressApp.get(`/${path}/static/styles.css`, (_req, res) => {
43
+ res.type('css').send(this.readAsset('styles.css'));
44
+ });
45
+ expressApp.get(`/${path}/static/client.js`, (_req, res) => {
46
+ res.type('js').send(this.readAsset('client.js'));
47
+ });
48
+ expressApp.get(`/${path}/api/keys`, async (req, res) => {
49
+ try {
50
+ const pattern = typeof req.query.pattern === 'string' && req.query.pattern
51
+ ? req.query.pattern
52
+ : defaultPattern;
53
+ const keys = (await store.keys(pattern)).sort((a, b) => a.localeCompare(b));
54
+ res.json({ pattern, count: keys.length, keys });
55
+ }
56
+ catch (error) {
57
+ res.status(500).json({
58
+ message: error instanceof Error ? error.message : 'Failed to fetch keys',
59
+ });
60
+ }
61
+ });
62
+ expressApp.get(`/${path}/api/entry`, async (req, res) => {
63
+ try {
64
+ const key = typeof req.query.key === 'string' ? req.query.key : '';
65
+ if (!key) {
66
+ res
67
+ .status(400)
68
+ .json({ message: 'key query parameter is required' });
69
+ return;
70
+ }
71
+ const entry = await store.getEntry(key);
72
+ res.json(entry);
73
+ }
74
+ catch (error) {
75
+ res.status(500).json({
76
+ message: error instanceof Error ? error.message : 'Failed to fetch entry',
77
+ });
78
+ }
79
+ });
80
+ if (allowDelete) {
81
+ expressApp.delete(`/${path}/api/entry`, async (req, res) => {
82
+ try {
83
+ const key = typeof req.query.key === 'string' ? req.query.key : '';
84
+ if (!key) {
85
+ res
86
+ .status(400)
87
+ .json({ message: 'key query parameter is required' });
88
+ return;
89
+ }
90
+ await store.delete(key);
91
+ res.json({ message: 'deleted', key });
92
+ }
93
+ catch (error) {
94
+ res.status(500).json({
95
+ message: error instanceof Error
96
+ ? error.message
97
+ : 'Failed to delete entry',
98
+ });
99
+ }
100
+ });
101
+ }
102
+ }
103
+ getHtml() {
104
+ const { path, title, badge, defaultPattern, allowDelete } = this.options;
105
+ return this.readAsset('index.html')
106
+ .replaceAll('__REDIS_UI_PATH__', path)
107
+ .replaceAll('__REDIS_UI_TITLE__', title)
108
+ .replaceAll('__REDIS_UI_BADGE__', badge)
109
+ .replaceAll('__REDIS_UI_DEFAULT_PATTERN__', defaultPattern)
110
+ .replaceAll('__REDIS_UI_ALLOW_DELETE__', String(allowDelete));
111
+ }
112
+ readAsset(filename) {
113
+ return (0, fs_1.readFileSync)((0, path_1.join)(this.assetDir, filename), 'utf-8');
114
+ }
115
+ }
116
+ exports.RedisUi = RedisUi;
117
+ //# sourceMappingURL=redis-ui.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-ui.js","sourceRoot":"","sources":["../../../src/redis/ui/redis-ui.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { RedisUiOptions, ResolvedRedisUiOptions } from '../interfaces/redis-ui-options.interface';
2
+ export declare const REDIS_UI_DEFAULTS: {
3
+ path: string;
4
+ title: string;
5
+ badge: string;
6
+ defaultPattern: string;
7
+ allowDelete: true;
8
+ auth: false;
9
+ };
10
+ export declare function resolveRedisUiOptions(options: RedisUiOptions): ResolvedRedisUiOptions;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.REDIS_UI_DEFAULTS = void 0;
4
+ exports.resolveRedisUiOptions = resolveRedisUiOptions;
5
+ exports.REDIS_UI_DEFAULTS = {
6
+ path: 'redis-ui',
7
+ title: 'Redis UI',
8
+ badge: 'CACHE',
9
+ defaultPattern: '*',
10
+ allowDelete: true,
11
+ auth: false,
12
+ };
13
+ function resolveRedisUiOptions(options) {
14
+ return {
15
+ enabled: options.enabled,
16
+ store: options.store,
17
+ path: options.path ?? exports.REDIS_UI_DEFAULTS.path,
18
+ title: options.title ?? exports.REDIS_UI_DEFAULTS.title,
19
+ badge: options.badge ?? exports.REDIS_UI_DEFAULTS.badge,
20
+ defaultPattern: options.defaultPattern ?? exports.REDIS_UI_DEFAULTS.defaultPattern,
21
+ allowDelete: options.allowDelete ?? exports.REDIS_UI_DEFAULTS.allowDelete,
22
+ auth: options.auth ?? exports.REDIS_UI_DEFAULTS.auth,
23
+ };
24
+ }
25
+ //# sourceMappingURL=resolve-redis-ui-options.js.map