feat(redis): edit hash fields

This commit is contained in:
onenewcode authored and GitHub committed 2026-08-26 14:44:19 +08:00
1 parent 75c2dc5e17
commit 0f9d32e1f5
18 files changed
+470 -14

No files matched your search

@@ -172,13 +172,13 @@ describe("native RedisJSON editor", () => {
expect(saveStringCalls).toContain("api.redisSetString");
expect(saveStringText).toMatch(/redisSetString\([\s\S]*\bvalue\b/);
// Hash JSON draft (editor open or retained after leaving JSON tab) compact-writes via HSET.
// Hash JSON draft (editor open or retained after leaving JSON tab) compact-writes via the atomic field update.
expect(saveMemberText).toContain("savingHashJson");
expect(saveMemberText).toContain('memberDraftFormat.value === "json"');
expect(saveMemberCalls).toContain("normalizeRedisJsonDraft");
expect(saveMemberText).toContain("writeValue = normalized.compactText");
expect(saveMemberCalls).toContain("api.redisHashSet");
expect(saveMemberText).toMatch(/redisHashSet\([\s\S]*\bwriteValue\b/);
expect(saveMemberCalls).toContain("api.redisHashFieldUpdate");
expect(saveMemberText).toMatch(/redisHashFieldUpdate\([\s\S]*\bwriteValue\b/);
// Editor pretty baseline also uses the source-preserving formatter.
expect(findFunction("formatJsonText").getText()).toContain("formatJsonSource");
@@ -208,12 +208,12 @@ describe("native RedisJSON editor", () => {
const saved = normalizeRedisJsonDraft(opened.json!.formattedText);
expect(saved).toEqual({ ok: true, compactText: DUPLICATE_MEMBER_COMPACT });
// Wiring: hash path only normalizes when savingHashJson, then HSETs writeValue.
// Wiring: hash path only normalizes when savingHashJson, then atomically updates the field with writeValue.
const saveMemberEdit = findFunction("saveMemberEdit");
const text = saveMemberEdit.getText();
expect(text).toContain("if (savingHashJson)");
expect(text).toContain("writeValue = normalized.compactText");
expect(callsIn(saveMemberEdit).map(calledName)).toEqual(expect.arrayContaining(["normalizeRedisJsonDraft", "api.redisHashSet"]));
expect(callsIn(saveMemberEdit).map(calledName)).toEqual(expect.arrayContaining(["normalizeRedisJsonDraft", "api.redisHashFieldUpdate"]));
});
it("keeps the native editor and export paths on the RedisJSON value-text contract", () => {
@@ -241,6 +241,11 @@ describe("native RedisJSON editor", () => {
const hashSearch = findFunction("onHashSearch");
const viewMember = findFunction("viewMember");
const setMemberValueFormat = findFunction("setMemberValueFormat");
const selectMember = findFunction("selectMember");
const startEditMember = findFunction("startEditMember");
const saveMemberEdit = findFunction("saveMemberEdit");
const memberValueChanged = findVariableInitializer("memberValueChanged");
const memberFieldChanged = findVariableInitializer("memberFieldChanged");
const unsavedDraft = findVariableInitializer("hasUnsavedRedisDraft");
const textFormat = findFunction("isTextRedisFormat");
const labels = templateElements(parsedViewer.descriptor.template!.ast as unknown as TemplateElement);
@@ -256,6 +261,14 @@ describe("native RedisJSON editor", () => {
expect(setMemberValueFormat.getText()).toContain('memberDraftFormat.value = "utf8"');
expect(unsavedDraft.getText()).toContain("hasRetainedStringDraft.value");
expect(unsavedDraft.getText()).toContain("hasRetainedMemberDraft.value");
expect(unsavedDraft.getText()).toContain("memberFieldChanged.value");
expect(memberFieldChanged.getText()).toContain("memberFieldDraftBaseline.value");
expect(memberValueChanged.getText()).toContain("memberFieldChanged.value");
expect(selectMember.getText()).toContain("memberFieldDraftBaseline.value");
expect(startEditMember.getText()).toContain("!memberFieldChanged.value");
expect(saveMemberEdit.getText()).toContain("memberFieldEditValue.value");
expect(saveMemberEdit.getText()).not.toContain("memberFieldEditValue.value.trim()");
expect(viewerSource).toContain(':disabled="savingMember || !memberValueChanged"');
expect(textFormat.getText()).toContain('format === "json"');
expect(labels.some((element) => element.tag === "label" && directiveExpression(element, "if") === "isTextRedisFormat(stringValueView) || activeStructuredStringDetail || isDecompressCodec(stringValueCodec)")).toBe(true);
expect(labels.some((element) => element.tag === "label" && directiveExpression(element, "if") === "isTextRedisFormat(memberValueView) || activeStructuredMemberDetail || isDecompressCodec(memberValueCodec)")).toBe(true);
+1
View File
@@ -468,6 +468,7 @@ export const redisDeleteKey = forward("redisDeleteKey");
export const redisRenameKey = forward("redisRenameKey");
export const redisHashSet = forward("redisHashSet");
export const redisHashDel = forward("redisHashDel");
export const redisHashFieldUpdate = forward("redisHashFieldUpdate");
export const redisHashFieldSetTtl = forward("redisHashFieldSetTtl");
export const redisHashFieldSetExpireAt = forward("redisHashFieldSetExpireAt");
export const redisListPush = forward("redisListPush");
+11
View File
@@ -2703,6 +2703,17 @@ export async function redisHashDel(connectionId: string, db: number, keyRaw: str
return post("/api/redis/hash-del", { connectionId, db, keyRaw, field });
}
export async function redisHashFieldUpdate(connectionId: string, db: number, keyRaw: string, oldField: string, newField: string, value: string): Promise<void> {
return post("/api/redis/hash-field-update", {
connectionId,
db,
keyRaw,
oldField,
newField,
value,
});
}
export async function redisHashFieldSetTtl(connectionId: string, db: number, keyRaw: string, field: string, ttl: number): Promise<void> {
return post("/api/redis/hash-field-set-ttl", { connectionId, db, keyRaw, field, ttl });
}
+11
View File
@@ -2590,6 +2590,17 @@ export async function redisHashDel(connectionId: string, db: number, keyRaw: str
return invoke("redis_hash_del", { connectionId, db, keyRaw, field });
}
export async function redisHashFieldUpdate(connectionId: string, db: number, keyRaw: string, oldField: string, newField: string, value: string): Promise<void> {
return invoke("redis_hash_field_update", {
connectionId,
db,
keyRaw,
oldField,
newField,
value,
});
}
export async function redisHashFieldSetTtl(connectionId: string, db: number, keyRaw: string, field: string, ttl: number): Promise<void> {
return invoke("redis_hash_field_set_ttl", { connectionId, db, keyRaw, field, ttl });
}